20

In bash we can iterate over index of an array like this

~$ for i in "${!test[@]}"; do echo $i; done

where test is an array, say,

~$ test=(a "b c d" e f)

so that the output looks like

0
1
2
3

However, when I do the same in zsh I get an error:

➜ ~ for i in "${!test[@]}"; do echo $i; done
zsh: event not found: test[@]

What is going on?

What is the proper way of iterating over indices in zsh?

GZ-
  • 317

3 Answers3

20

zsh arrays are normal arrays like those of most other shells and languages, they are not like in ksh/bash associative arrays with keys limited to positive integers (aka sparse arrays). zsh has a separate variable type for associative arrays (with keys being arbitrary sequences of 0 or more bytes).

So the indices for normal arrays are always integers ranging from 1 to the size of the array (assuming ksh compatibility is not enabled in which case array indices start at 0 instead of 1).

So:

typeset -a array
array=(a 'b c' '')
for ((i = 1; i <= $#array; i++)) print -r -- $array[i]

Though generally, you would loop over the array members, not over their indices:

for i ("$array[@]") print -r -- $i

(the "$array[@]" syntax, as opposed to $array, preserves the empty elements).

Or:

print -rC1 -- "$array[@]"

to pass all the elements to a command.

Now, to loop over the keys of an associative array, the syntax is:

typeset -A hash
hash=(
  key1  value1
  key2  value2
  ''    empty
  empty ''
)
for key ("${(@k)hash}") printf 'key=%s value=%s\n' "$key" "$hash[$key]"

(with again @ inside quotes used to preserve empty elements).

Though you can also pass both keys and values to commands with:

printf 'key=%s value=%s\n' "${(@kv)hash}"

For more information on the various array designs in Bourne-like shells, see Test for array support by shell

5

As said in this article A User's Guide to the Z-Shell - Chapter 5: Substitutions:

This is extended for other parameters in the following way:

% array=(one two three)
% print -l "${array[@]}"
one
two
three

and more generally for all forms of substitution using another flag, (@):

% print -l "${(@)array}"
one
two
three

So, maybe just try using second way?

  • Hi Anaoliy, I used the wrong example output for my question which was misleading. Terribly sorry about that! I've edited the question. – GZ- Aug 24 '20 at 16:19
  • 2
    Note that forgetting the -- to delimit options from arguments to print is very dangerous in zsh. That introduces command injection vulnerabilities as processing of some options can cause commands to be executed like with array=('-va[$(reboot)1]') here. You also forgot the -r option. To print the elements one per line, use print -rC1 -- "$array[@]" as shown in my answer. print -l prints an empty line when not passed any argument. – Stéphane Chazelas May 08 '23 at 09:39
5

And with curly brackets { }:

% test=(a "b c d" e f)                 
% for i in {1..$#test}; do echo $i; done
1
2
3
4
%
Zach Young
  • 220
  • 2
  • 5