In some recent work I noticed that Zsh's and ksh's bang (!) expansion flags have different meanings. I don't think this is problematic but other people might disagree and as this difference wasn't obvious to me I thought it was worth mentioning here.
In Zsh, the (!) flag means "Don't dereference references", while in ksh it means "Print the parameter's name instead of its value". The difference is the most striking on chains of references:
Zsh | Zsh in ksh emulation | ksh |
typeset var0=foo typeset -n ref1=var0 ref2=ref1 ref3=ref2 echo var0:${(!)var0} echo ref1:${(!)ref1} echo ref2:${(!)ref2} echo ref3:${(!)ref3} | emulate ksh typeset var0=foo typeset -n ref1=var0 ref2=ref1 ref3=ref2 echo var0:${!var0} echo ref1:${!ref1} echo ref2:${!ref2} echo ref3:${!ref3} |
typeset var0=foo typeset -n ref1=var0 ref2=ref1 ref3=ref2 echo var0:${!var0} echo ref1:${!ref1} echo ref2:${!ref2} echo ref3:${!ref3} |
var0:foo ref1:var0 ref2:ref1 ref3:ref2 | var0:foo ref1:var0 ref2:ref1 ref3:ref2 | var0:var0 ref1:var0 ref2:var0 ref3:var0 |
In Zsh, the (!) flag only makes a difference when the parameter is a reference, while in ksh it always makes a difference; it always replaces the parameter's value with the parameter's name. In ksh, the (!) flag can be used to list the keys of arrays and associative arrays. In that regard, it's closer to Zsh's (k) flag than Zsh's (!) flag:
Zsh | Zsh in ksh emulation | ksh |
typeset str=abc typeset -a arr=(aa bb cc) typeset -A ass=(X xx Y yy Z zz) echo "str :${(k)str}" echo "str[1]:${(k)str[1]}" echo "str[*]:${(k)str[*]}" echo "arr :${(k)arr}" echo "arr[1]:${(k)arr[1]}" echo "arr[*]:${(k)arr[*]}" echo "ass :${(k)ass}" echo "ass[Y]:${(k)ass[Y]}" echo "ass[*]:${(k)ass[*]}" | emulate ksh typeset str=abc typeset -a arr=(aa bb cc) typeset -A ass=([X]=xx [Y]=yy [Z]=zz) echo "str :${!str}" echo "str[1]:${!str[1]}" echo "str[*]:${!str[*]}" echo "arr :${!arr}" echo "arr[1]:${!arr[1]}" echo "arr[*]:${!arr[*]}" echo "ass :${!ass}" echo "ass[Y]:${!ass[Y]}" echo "ass[*]:${!ass[*]}" |
typeset str=abc typeset -a arr=(aa bb cc) typeset -A ass=([X]=xx [Y]=yy [Z]=zz) echo "str :${!str}" echo "str[1]:${!str[1]}" echo "str[*]:${!str[*]}" echo "arr :${!arr}" echo "arr[1]:${!arr[1]}" echo "arr[*]:${!arr[*]}" echo "ass :${!ass}" echo "ass[Y]:${!ass[Y]}" echo "ass[*]:${!ass[*]}" |
str :abc str[1]:a str[*]:abc arr :aa bb cc arr[1]:1 arr[*]:aa bb cc ass :X Y Z ass[Y]:Y ass[*]:X Y Z | str :abc str[1]:b str[*]:abc arr :aa arr[1]:1 arr[*]:aa bb cc ass : ass[Y]:Y ass[*]:X Y Z | str :str str[1]:str[1] str[*]:0 arr :arr arr[1]:arr[1] arr[*]:0 1 2 ass :ass ass[Y]:ass[Y] ass[*]:X Y Z |
One difference is that in Zsh "${(k)ass[Y]}" returns just "Y", while in ksh "${!ass[Y]}" returns "ass[Y]". To me, the former looks more consistent with the behavior for "ass[*]".
Another difference is that in Zsh the (k) flag doesn't work for array ranges ("${(k)arr[*]}" returns "aa bb cc"), while in ksh it does ("${!arr[*]}" returns "0 1 2"). To me, this looks like something that ought to be changed in Zsh. In conjunction with the (v) flag, it could be used to list array values interleaved with their indexes.
Philippe