A snippet is moved from bash to zsh: `a=(x y z); echo ${a[1]}; echo ${#a}`. What does each line print under each shell, and why do they differ?
answer
- one shell counts from one
- a bare array name is not the same thing
- length of what, exactly
- the [@] form is the safe spelling
- an option named after another shell
basics
~10 szsh prints x then 3: its arrays are 1-indexed and ${#a} counts elements. bash prints y then 1: its arrays are 0-indexed and ${#a} is the length of element zero, not the element count.
solid answer
~40 szsh arrays follow the ksh/csh tradition and start at index 1, so `${a[1]}` is `x`. bash arrays are 0-indexed like C, so `${a[1]}` is the second element, `y`. The second line is the nastier difference: in zsh `${#a}` is the number of elements, 3, while in bash a bare array name means element zero, so `${#a}` is the *string length* of `x`, which is 1. Both quirks are silent — nothing errors, you just get wrong values. The portable spellings are `"${a[@]}"` for all elements and `${#a[@]}` for the count, which mean the same thing in both shells. zsh can be switched with `setopt KSH_ARRAYS`, which makes subscripts 0-based and requires braces, but that changes behaviour shell-wide, so prefer writing the portable form.
code
bash · 6 lines# these four expansions mean the same thing in bash and zsh
a=(x y z)
print -r -- "${a[@]}" # x y z
print -r -- ${#a[@]} # 3
a+=(w) # append
for e in "${a[@]}"; do print -r -- "$e"; donego deeper
Remember that zsh arrays start at 1 and bash arrays start at 0, and that "${a[@]}" is the safe way to say "all elements" whichever shell you are in.
Explain the underlying rule — what a bare array name means — and derive from it why ${#a} is an element count in zsh but a string length in bash, plus why echo $a differs.
Point out that both differences fail silently rather than erroring, and describe how you would keep shared functions in the portable subset ("${a[@]}", ${#a[@]}, a+=(...)) rather than relying on either shell's native form.
Own the decision about whether shared shell code should exist at all: argue for a portable subset with a linting rule, or for shell-specific files with no sourcing across shells, rather than papering over the difference with a global KSH_ARRAYS.
## The four values | expression | zsh | bash | |---|---|---| | `${a[1]}` | `x` | `y` | | `${#a}` | `3` | `1` | | `${a[@]}` | `x y z` | `x y z` | | `${#a[@]}` | `3` | `3` | Both differences are silent. Nothing warns you; you simply read the wrong element and get the wrong count. ## Why the indices differ zsh inherited 1-based array indexing from the ksh and csh lineage, where the first element of a list is element one. bash chose 0-based indexing to match C. Neither is wrong; they are just incompatible, and a loop written as `for ((i=0; i<n; i++)); do print $a[i]; done` reads a nonexistent element zero in zsh on its first iteration. In zsh, `$a[0]` is empty by default (referencing it is not an error unless you have set the `NO_UNSET` option). zsh also supports negative subscripts — `$a[-1]` is the last element — and *slices*: `$a[2,3]` is elements two through three inclusive. bash spells a slice differently, `${a[@]:1:2}`, meaning "two elements starting at offset one", and has supported negative subscripts only since bash 4.3. zsh additionally lets you subscript without braces: `$a[2]` is valid zsh and is a syntax error's worth of trouble in bash, where you must write `${a[2]}`. ## Why `${#a}` differs — the subtler trap In bash, a bare array name in an expansion is shorthand for element zero. `$a` is `${a[0]}`, and therefore `${#a}` is `${#a[0]}`, the number of characters in the first element. With `a=(x y z)` that is 1; with `a=(hello world)` it is 5, which looks plausible enough to pass a casual review. In zsh, a bare array name means the whole array. `$a` expands to all elements (one word each, since zsh does not word-split but does expand arrays element-wise), and `${#a}` is the element count. That is the same underlying disagreement expressed twice: bash treats an array name as a scalar view onto element zero, zsh treats it as the list. It explains a third difference too — `echo $a` prints `x` in bash and `x y z` in zsh. ## The portable subset ```zsh a=(x y z) print -r -- "${a[@]}" # all elements, both shells print -r -- "${#a[@]}" # element count, both shells a+=(w) # append, both shells for e in "${a[@]}"; do print -r -- "$e"; done # both shells ``` If a file has to be sourced by both shells, restrict yourself to those forms and never use a bare `$a` or a numeric literal subscript. Associative arrays exist in both — `typeset -A m` in zsh, `declare -A m` in bash 4.0+ — but their iteration and key-expansion syntaxes diverge further, so shared code is usually better off avoiding them. ## KSH_ARRAYS and when to use it zsh offers `setopt KSH_ARRAYS`, which makes subscripts 0-based, makes a bare `$a` mean element zero, and requires braces around subscripts — in other words, bash's rules. It is the right tool in exactly one situation: running a block of ksh- or bash-derived code inside zsh, ideally scoped with `emulate -L ksh` inside a function so the change does not leak into the rest of the shell. It is the wrong tool as a global setting in an interactive `.zshrc`, because every zsh completion function, plugin, and snippet you paste from the internet assumes the native 1-based rules and will misbehave. ## What the interviewer is testing The indexing difference alone is trivia. What separates answers is noticing the second line: candidates who confidently say `${#a}` is the length in both shells, or the count in both shells, are guessing. The good answer names the underlying rule — *what a bare array name means* — and derives all three consequences from it.
- What does `echo $a` print in each shell for `a=(x y z)`?bash prints `x`, because a bare array name is shorthand for element zero. zsh prints `x y z`, because a bare array name means the whole array and each element becomes its own word. It is the same rule that explains why `${#a}` is a string length in bash and an element count in zsh.
- When is `setopt KSH_ARRAYS` the right call?Only when you need to run ksh- or bash-derived code unchanged inside zsh, and then scoped — `emulate -L ksh` inside a function localises it. Setting it globally in an interactive configuration breaks zsh completion functions and virtually every plugin, all of which assume native 1-based subscripts.
- How do slices differ between the two shells?zsh uses inclusive index ranges: `$a[2,3]` is the second through third elements. bash uses offset and length: `${a[@]:1:2}` is two elements starting at offset one. They can select the same elements, but the numbers mean different things, so a slice copied between shells is silently off by one.
saying these in an interview costs you the question
- Assuming every shell indexes arrays from zero
- Saying ${#a} is the element count in both shells
- Using a bare $a to mean all elements in bash
- Copying a C-style for loop starting at index 0 into zsh
- Treating zsh slice ranges as bash offset-and-length