Scripting differences from bash

Zsh · Advanced · 12 min

Pasting bash snippets into zsh (or the reverse) breaks in subtle ways unless you know these differences.

Zsh scripts look like bash scripts, but several defaults differ:

  • No word splitting of unquoted variables. With v="a b", $v stays one word. Use ${=v} to split explicitly, or setopt SH_WORD_SPLIT. Globs inside variable values are not expanded either unless you write ${~v}.
  • Arrays are 1-based: $arr[1] is the first element, $arr[-1] the last, $#arr the length, $arr[2,3] a slice. $arr alone gives all elements, and empty elements are dropped unless you use "${arr[@]}".
  • Associative arrays: typeset -A m; ${(k)m} lists keys, ${(v)m} values.
  • read -A (not -a) reads words into an array.
  • The last stage of a pipeline runs in the current shell, so cmd | read v sets v.
  • An unmatched glob is an error (NOMATCH); use (N).

Prefer print over echo (print -r -- for raw output, print -l one argument per line); like zsh's echo, plain print interprets backslash escapes.

For code that must behave like sh or ksh, emulate -L sh inside a function switches to those rules for that function only (-L makes options local). Conversely, libraries meant to work under any user configuration start with emulate -L zsh to get default zsh options.

Example

words="one two three"
for w in $words; do print -- "<$w>"; done              # <one two three>
for w in ${=words}; do print -n -- "<$w>"; done; print # <one><two><three>
arr=(alpha beta gamma)
print $arr[1] $arr[-1] $#arr          # alpha gamma 3   (1-based)
print $arr[2,3]                       # beta gamma
typeset -A port=(http 80 https 443)
print $port[https]                    # 443
print ${(k)port}                      # https http  (order not guaranteed)
echo hi | read v; print "v=$v"        # v=hi   (in bash: v=)
read -A parts <<< "x y z"; print $#parts   # 3
f() { emulate -L sh; s="a b"; set -- $s; print $#; }
f                                     # 2   (sh rules: word splitting on)
print -l -- *.nomatch(N) done         # done

Remember

  • Unquoted $var is not split in zsh; ${=var} splits explicitly
  • Arrays start at 1; $#arr is the length
  • emulate -L sh or zsh sets predictable rules inside a function

Watch out for

  • Porting a bash loop over $list and getting a single iteration because zsh does not split it.

Check yourself

  1. In zsh, with arr=(a b c), what is $arr[1]?
  2. In zsh, `v="x y"; for i in $v; do print $i; done` prints how many lines?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: zsh.sourceforge.io/Doc/Release/Parameters.html