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

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



As I've mentioned in the past, if you see ksh-like behavior in zsh that doesn't quite match ksh, it's often because the ksh behavior was implemented based on reading examples from the ksh manual pages way back when ksh was proprietary software and unavailable for running a comparison.

Other times it's because the ksh behavior or syntax conflicted with something else zsh was already doing.

For the case ${!vname[subscript]}, I think that Zsh's ksh emulation is actually closer to ksh's documentation than ksh itself. Here is ksh's documentation about ${!vname[subscript]} from https://manpages.debian.org/trixie/ksh93u+m/ksh93.1.en.html:

${!vname[subscript]}
Expands to name of the subscript unless subscript is *, @. or of the form sub1 .. sub2. When subscript is *, the list of array subscripts for vname is generated. For a variable that is not an array, the value is 0 if the variable is set. Otherwise it is the empty string. When subscript is @, same as above, except that when used in double quotes, each array subscript yields a separate argument. When subscript is of the form sub1 .. sub2 it expands to the list of subscripts between sub1 and sub2 inclusive using the same quoting rules as @.

Also consider this (ksh code):

$ typeset arr=(aa bb cc)                                        
$ echo ${!arr[@]}
0 1 2                 
# Names of the subscripts
$ echo ${!arr[1..2]}
1 2                    # Names of the subscripts
$ echo ${!arr[1..1]}
1                      
# Names of the subscript
$ echo ${!arr[1]}  
arr[1]                 
# What?!?

--- expected
+++ actual
@@ -1,3 +1,3 @@
+characters
 .k02.array
-.k02.array
 characters
Test K02parameter.ztst failed: output differs from expected as shown above for:
  () {
    typeset -n .k02.ref=.k02.array
    emulate -L ksh
    print -l ${!.k02.ref} ${(!).k02.ref} ${.k02.ref}
  }
Was testing: namerefs with namespaces
K02parameter.ztst: test failed.

Oops, I forgot to run the whole test suite. Here is an updated patch:

Don't map ksh's ! to Zsh's (!k), map it to (k)

> 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.

This doesn't look right to me?  The test is prefixed with

  .k02.array=(characters in an array)

${!.k02.ref} should not return "characters" ...?  That makes '!' just
a no-op for this case.

Let's forget about namespaces for a moment and consider the following definitions:

typeset arr0=(abc def ghi)
typeset -n ref1=arr0
typeset -n ref2=ref1


It's true that with my patch (which maps ${!name…} to ${(k)name…}), the ! in "${!ref1}" becomes a no-op. But why only complain about the no-opity of ! in "${!ref1}" and not in "${!arr0}"? Ksh expands both to "arr0", not just the former. In fact, ksh also expands "${!ref2}" to "arr0", while without my patch Zsh expands it to "ref1". With my patch, Zsh uniformly handles ! as a no-op in ${!name} while currently it sometimes performs something and sometimes that something happens to match what ksh does. That doesn't sound very compelling to me.

Furthermore, the current behavior maps "${!arr0[1]}", "${!ref1[1]}", and "${!ref2[1]}" to "1", "r", and "e", while my patch maps all of them to "1", which is much closer to ksh's behavior of mapping all of them to "arr0[1]".

If we want to bring Zsh's emulation of ${!name…} closer to ksh's behavior, then instead of mapping ${!name…} to ${(!k)name…}, I would rather adopt my patch to map it to ${(k)name…} and extend (k) to plain variables in ksh emulation. The following patch does that:

- In ksh emulation, extend (k) expansion flag to plain variable names (DO NOT COMMIT - just a proof of concept)
 
With that patch, all of "${!arr0}", "${!ref1}", and "${!ref2}" expand to "arr0", like in ksh. The comparison with ksh becomes much greener:


Zsh's ksh emulation

ksh

Zsh's ksh emulation


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


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

+ extend (k) in ksh emulation

!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

str0 - str0 - str0

b - b - b

bc - bc - bc

abc - abc - abc


arr0 - arr0 - arr0

1 - 1 - 1

<error> - <error> - <error>

aa bb cc - aa bb cc - aa bb cc


hsh0 - hsh0 - hsh0

1 - 1 - 1

0 1 2 - 0 1 2 - 0 1 2


For the time being, I would revert ${!name…} to ${(k)name…} (as it used to be before named references) and later discuss whether we want to extend (k) to plain variables in ksh emulation.

Note that (k) can only be extended to plain variables in ksh emulation because in Zsh ${hash} expands to all values of hash, so ${(k)hash} has to expand to all keys of hash, it can't expand to "hash".

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 0e1cc9e4e..bfc8ee508 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -2363,4 +2363,59 @@ F:converting from association/array to string should work here too
  typeset -Hn ref_H
 0:unsusual but legal option combinations
 
+ 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
diff --git a/Test/K02parameter.ztst b/Test/K02parameter.ztst
index f9f434b36..2ae711e76 100644
--- a/Test/K02parameter.ztst
+++ b/Test/K02parameter.ztst
@@ -93,12 +93,25 @@ F:Braces are required
   () {
     typeset -n .k02.ref=.k02.array
     emulate -L ksh
-    print -l ${!.k02.ref} ${(!).k02.ref} ${.k02.ref}
+    print -l ${!.k02.ref} ${(k).k02.ref} ${(!).k02.ref} ${.k02.ref}
+    print -l ${!.k02.ref[2]} ${(k).k02.ref[2]} ${(!).k02.ref[2]} ${.k02.ref[2]}
   }
 0:namerefs with namespaces
+F:ksh expands "${!.k02.ref}" to ".k02.ref" but ("${!ref}" when in namespace "k02") to "array"
+F:the former looks bogus; expanding to ".k02.array" looks more logical/consistent
+>characters
+>characters
 >.k02.array
->.k02.array
+F:ksh expands "${.k02.ref}" to ".k02.array" but ("${ref}" when in namespace "k02") to "characters"
+F:the former looks bogus; expanding to "characters" looks more logical/consistent
 >characters
+F:ksh expands "${!.k02.ref[2]}" and ("${!ref[2]}" when in namespace "k02") to "array[2]"
+>2
+>2
+F:"${(!).k02.ref[2]}" -> "${${:-.k02.array}[2]}" -> "0"
+>0
+F:ksh expands "${.k02.ref[2]}" and ("${ref[2]}" when in namespace "k02") to "an"
+>an
 
   k.=empty
   k.2=test
diff --git a/Src/subst.c b/Src/subst.c
index efbd81105..f85eeb778 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -2763,6 +2763,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
      */
     if (!subexp || aspar) {
 	char *ov = val;
+	char *ie = itype_end(subexp ? ov : s, inbrace ? INAMESPC : IIDENT, 0);
+	int nobracks = *ie != '[' && *ie != Inbrack;
 	int scanflags = hkeys | hvals;
 	if (arrasg)
 	    scanflags |= SCANPM_ASSIGNING;
@@ -2859,6 +2861,14 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 	    v = NULL;
 	    isarr = 0;
 	}
+	if (EMULATION(EMULATE_KSH) && (hkeys & SCANPM_WANTKEYS) && nobracks &&
+	    v && v->pm && ((v->pm->node.flags & PM_DECLARED) ||
+			   !(v->pm->node.flags & PM_UNSET)) ) {
+	    val = dupstring(v->pm->node.nam);
+	    vunset = 0;
+	    v = NULL;
+	    isarr = 0;
+	}
     }
     /*
      * We get in here two ways; either we need to convert v into
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index bfc8ee508..6840e6974 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -2406,15 +2406,15 @@ F:converting from association/array to string should work here too
 >bb - bb - bb
 >aa bb cc - aa bb cc - aa bb cc
 >
->abc - abc - abc
+>str0 - str0 - str0
 >b - b - b
 >bc - bc - bc
 >abc - abc - abc
->aa - aa - aa
+>arr0 - arr0 - arr0
 >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
+>hsh0 - hsh0 - hsh0
 >1 - 1 - 1
 >0 1 2 - 0 1 2 - 0 1 2
 
diff --git a/Test/K02parameter.ztst b/Test/K02parameter.ztst
index 2ae711e76..91ce2520f 100644
--- a/Test/K02parameter.ztst
+++ b/Test/K02parameter.ztst
@@ -99,8 +99,8 @@ F:Braces are required
 0:namerefs with namespaces
 F:ksh expands "${!.k02.ref}" to ".k02.ref" but ("${!ref}" when in namespace "k02") to "array"
 F:the former looks bogus; expanding to ".k02.array" looks more logical/consistent
->characters
->characters
+>.k02.array
+>.k02.array
 >.k02.array
 F:ksh expands "${.k02.ref}" to ".k02.array" but ("${ref}" when in namespace "k02") to "characters"
 F:the former looks bogus; expanding to "characters" looks more logical/consistent


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