shell-mastery — bash vs zsh
bash vs zsh — Key Differences
Arrays (the #1 gotcha)
# bash: 0-indexed
arr=(a b c); echo ${arr[0]} # a
# zsh: 1-indexed
arr=(a b c); echo ${arr[1]} # a
Glob behavior
# bash: no match = literal string
echo *.xyz # *.xyz (if no match)
# zsh: no match = error (nomatch option)
echo *.xyz # zsh: no matches found: *.xyz
setopt nonomatch # disable to match bash behavior
Word splitting
# bash: unquoted variable expansion splits on IFS
x="a b c"; for i in $x; do echo $i; done # 3 lines
# zsh: no implicit splitting (safer)
x="a b c"; for i in $x; do echo $i; done # 1 line: "a b c"
x="a b c"; for i in ${=x}; do echo $i; done # ${=x} forces split
Associative arrays
# bash: declare -A, iterate with ${!arr[@]}
declare -A m=([k1]=v1 [k2]=v2)
for key in "${!m[@]}"; do echo "$key=${m[$key]}"; done
# zsh: declare -A, iterate with ${(k)arr}
declare -A m=([k1]=v1 [k2]=v2)
for key in "${(k)m[@]}"; do echo "$key=${m[$key]}"; done
Process substitution
# Both support <() but zsh may hit noclobber with >|
diff <(cmd1) <(cmd2) # works in both
cat file >| other # zsh needs >| if noclobber is on
Extended globbing
# bash: needs shopt -s extglob
shopt -s extglob
ls !(*.log) # everything except .log files
# zsh: native extended glob
setopt extendedglob
ls ^*.log # same thing, different syntax
ls **/*.py # recursive glob (zsh native, bash needs globstar)
Prompt
# bash: PS1 with escape codes
PS1='\u@\h:\w\$ '
# zsh: PROMPT with % codes
PROMPT='%n@%m:%~%# '