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:%~%# '