Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: Zsh's and ksh's bang (!) expansion flags have different meanings



Below is a patch that changes Zsh's emulation of ksh to map ksh's ! to just (k) instead of (!k). Note that the flag combination (!k) is forbidden, which I think is right.

Here is the ksh script that I used to compare Zsh's before and after patch ksh emulation:

typeset    str0=abc
typeset -a arr0=(aa bb cc)
typeset -A hsh0=([0]=aa [1]=bb [2]=cc)

typeset -n str1=str0 str2=str1
typeset -n arr1=arr0 arr2=arr1
typeset -n hsh1=hsh0 hsh2=hsh1

# Regular expansions

echo "${str0} - ${str1} - ${str2}"
echo "${str0[1]} - ${str1[1]} - ${str2[1]}"
echo "${str0[1,2]} - ${str1[1,2]} - ${str2[1,2]}"
echo "${str0[@]} - ${str1[@]} - ${str2[@]}"

echo "${arr0} - ${arr1} - ${arr2}"
echo "${arr0[1]} - ${arr1[1]} - ${arr2[1]}"
echo "${arr0[1,2]} - ${arr1[1,2]} - ${arr2[1,2]}"
echo "${arr0[@]} - ${arr1[@]} - ${arr2[@]}"

echo "${hsh0} - ${hsh1} - ${hsh2}"
echo "${hsh0[1]} - ${hsh1[1]} - ${hsh2[1]}"
echo "${hsh0[@]} - ${hsh1[@]} - ${hsh2[@]}"

# Bang expansions

echo "${!str0} - ${!str1} - ${!str2}"
echo "${!str0[1]} - ${!str1[1]} - ${!str2[1]}"
echo "${!str0[1,2]} - ${!str1[1,2]} - ${!str2[1,2]}"
echo "${!str0[@]} - ${!str1[@]} - ${!str2[@]}"

echo "${!arr0} - ${!arr1} - ${!arr2}"
echo "${!arr0[1]} - ${!arr1[1]} - ${!arr2[1]}"
echo "${!arr0[1,2]} - ${!arr1[1,2]} - ${!arr2[1,2]}"
echo "${!arr0[@]} - ${!arr1[@]} - ${!arr2[@]}"

echo "${!hsh0} - ${!hsh1} - ${!hsh2}"
echo "${!hsh0[1]} - ${!hsh1[1]} - ${!hsh2[1]}"
echo "${!hsh0[@]} - ${!hsh1[@]} - ${!hsh2[@]}"


Let's first have a look at regular expansions:


Zsh's ksh emulation

ksh

Zsh's ksh emulation


${!var} <=> ${(!k)var}


${!var} <=> ${(k)var}

str

str[1]

str[1,2]

str[@]


arr

arr[1]

arr[1,2]

arr[@]


hsh

hsh[1]

hsh[@]

abc - abc - abc

b - b - b

bc - bc - bc

abc - abc - abc


aa - aa - aa

bb - bb - bb

bb cc - bb cc - bb cc

aa bb cc - aa bb cc - aa bb cc


aa - aa - aa

bb - bb - bb

aa bb cc - aa bb cc - aa bb cc

abc - abc - abc

 -  - 

 -  - 

abc - abc - abc


aa - aa - aa

bb - bb - bb

cc - cc - cc

aa bb cc - aa bb cc - aa bb cc


aa - aa - aa

bb - bb - bb

aa bb cc - aa bb cc - aa bb cc

abc - abc - abc

b - b - b

bc - bc - bc

abc - abc - abc


aa - aa - aa

bb - bb - bb

bb cc - bb cc - bb cc

aa bb cc - aa bb cc - aa bb cc


aa - aa - aa

bb - bb - bb

aa bb cc - aa bb cc - aa bb cc


I highlighted in green the results that match the ksh results. As expected, the results are the same before and after the patch but there are already cases where they don't match the ksh results. That's because in ksh, strings are NOT equated to arrays of characters and thus subscripts other than [@] and [*] don't produce any result. Furthermore, in ksh, ${var[A,B]} is equivalent to ${var[((A,B))]} and thus still returns a single element rather than a range of elements like in Zsh.

Now let's see what happens for bang expansions:


Zsh's ksh emulation

ksh

Zsh's ksh emulation


${!var} <=> ${(!k)var}


${!var} <=> ${(k)var}

!str

!str[1]

!str[1,2]

!str[@]


!arr

!arr[1]

!arr[1,2]

!arr[@]


!hsh

!hsh[1]

!hsh[@]

abc - str0 - str1

b - t - t

bc - tr - tr

abc - str0 - str1


aa - arr0 - arr1

1 - r - r

<error> - rr - rr

aa bb cc - arr0 - arr1


aa - hsh0 - hsh1

1 - s - s

0 1 2 - hsh0 - hsh1

str0 - str0 - str0

str0[1] - str1[1] - str2[1]

str0[1,2] - str1[1,2] - str2[1,2]

0 - 0 - 0


arr0 - arr0 - arr0

arr0[1] - arr0[1] - arr0[1]

arr0[2] - arr0[2] - arr0[2]

0 1 2 - 0 1 2 - 0 1 2


hsh0 - hsh0 - hsh0

hsh0[1] - hsh0[1] - hsh0[1]

0 1 2 - 0 1 2 - 0 1 2

abc - abc - abc

b - b - b

bc - bc - bc

abc - abc - abc


aa - aa - aa

1 - 1 - 1

<error> - <error> - <error>

aa bb cc - aa bb cc - aa bb cc


aa - aa - aa

1 - 1 - 1

0 1 2 - 0 1 2 - 0 1 2


I highlighted in orange the results where Zsh does something similar (return the index/key) but not in the exact same way as ksh (Zsh returns just the index/key, while ksh returns the full parameter spec). The <error> string indicates cases where the expansion fails with an error.

We can see that there are very few cases, either before or after the patch, where Zsh's emulation matches ksh. In my opinion the results with the patch are more compelling. It also leaves open the possibility to have more matches in the future. Although Bart says it would be difficult, maybe we can extend (k) to array ranges, or at least to [@] and [*]. With that the last line of the array group would match. Another possibility is to extend (k), at least in ksh emulation, to also work for plain values. This looks feasible to me. The value structure computed by expansions typically contains a reference to the expanded parameter (the referred one, not the starting one), from which one can access its name. With that, the first line of each group would match.


Philippe

diff --git a/Src/subst.c b/Src/subst.c
index 1b75c035a..efbd81105 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -2118,7 +2118,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 	 * handle this with the ^, =, ~ stuff, below.
 	 */
 	if ((c = *s) == '!' && s[1] != Outbrace && EMULATION(EMULATE_KSH)) {
-	    hkeys = SCANPM_WANTKEYS|SCANPM_NONAMEREF;
+	    hkeys = SCANPM_WANTKEYS;
 	    s++;
 	    /* There's a slew of other special bash meanings of parameter
 	     * references that start with "!":
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index 95b8e5dbc..894e02c0a 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -2349,4 +2349,59 @@ F:converting from association/array to string should work here too
 >B:16#FF
 >C:16#FF
 
+ typeset str0=abc
+ typeset -a arr0=(aa bb cc)
+ typeset -A hsh0=([0]=aa [1]=bb [2]=cc)
+ typeset -n str1=str0 arr1=arr0 hsh1=hsh0
+ typeset -n str2=str1 arr2=arr1 hsh2=hsh1
+ emulate ksh -c '
+   echo "${str0} - ${str1} - ${str2}"
+   echo "${str0[1]} - ${str1[1]} - ${str2[1]}"
+   echo "${str0[1,2]} - ${str1[1,2]} - ${str2[1,2]}"
+   echo "${str0[@]} - ${str1[@]} - ${str2[@]}"
+   echo "${arr0} - ${arr1} - ${arr2}"
+   echo "${arr0[1]} - ${arr1[1]} - ${arr2[1]}"
+   echo "${arr0[1,2]} - ${arr1[1,2]} - ${arr2[1,2]}"
+   echo "${arr0[@]} - ${arr1[@]} - ${arr2[@]}"
+   echo "${hsh0} - ${hsh1} - ${hsh2}"
+   echo "${hsh0[1]} - ${hsh1[1]} - ${hsh2[1]}"
+   echo "${hsh0[@]} - ${hsh1[@]} - ${hsh2[@]}"
+   echo
+   echo "${!str0} - ${!str1} - ${!str2}"
+   echo "${!str0[1]} - ${!str1[1]} - ${!str2[1]}"
+   echo "${!str0[1,2]} - ${!str1[1,2]} - ${!str2[1,2]}"
+   echo "${!str0[@]} - ${!str1[@]} - ${!str2[@]}"
+   echo "${!arr0} - ${!arr1} - ${!arr2}"
+   echo "${!arr0[1]} - ${!arr1[1]} - ${!arr2[1]}"
+   echo "$(exec 2>&1; echo ${!arr0[1,2]}) - $(exec 2>&1; echo ${!arr1[1,2]}) - $(exec 2>&1; echo ${!arr2[1,2]})"
+   echo "${!arr0[@]} - ${!arr1[@]} - ${!arr2[@]}"
+   echo "${!hsh0} - ${!hsh1} - ${!hsh2}"
+   echo "${!hsh0[1]} - ${!hsh1[1]} - ${!hsh2[1]}"
+   echo "${!hsh0[@]} - ${!hsh1[@]} - ${!hsh2[@]}"
+ '
+0:emulation of ksh's bang always dereferences the expanded parameter
+>abc - abc - abc
+>b - b - b
+>bc - bc - bc
+>abc - abc - abc
+>aa - aa - aa
+>bb - bb - bb
+>bb cc - bb cc - bb cc
+>aa bb cc - aa bb cc - aa bb cc
+>aa - aa - aa
+>bb - bb - bb
+>aa bb cc - aa bb cc - aa bb cc
+>
+>abc - abc - abc
+>b - b - b
+>bc - bc - bc
+>abc - abc - abc
+>aa - aa - aa
+>1 - 1 - 1
+>(eval):6: invalid subscript - (eval):6: invalid subscript - (eval):6: invalid subscript
+>aa bb cc - aa bb cc - aa bb cc
+>aa - aa - aa
+>1 - 1 - 1
+>0 1 2 - 0 1 2 - 0 1 2
+
 %clean


Messages sorted by: Reverse Date, Date, Thread, Author