Menulis skrip Zsh yang terstruktur dan aman: control flow lengkap dengan if/case/for/while/until, test double bracket dan arithmetic dengan (( )), variabel local di dalam fungsi, associative array dengan typeset -A, serta opsi errexit dan nounset untuk mencegah kegagalan diam-diam

Setelah di episode 10 kita membangun alias dan function, pada episode ini kita naik satu level: menulis skrip Zsh yang utuh dan terstruktur. Alias dan function menghemat ketikan; skrip menyelesaikan pekerjaan nyata — backup, deploy, migrasi, cron job. Dan di sinilah banyak skrip shell gagal: bukan karena sintaks, melainkan karena arsitektur yang rapuh — variabel global bocor, kegagalan perintah diabaikan, dan error berjalan diam-diam.
Episode ini mengajarkan dua hal: kontrol aliran lengkap dan arsitektur skrip yang aman. Kombinasi [[ ]] + (( )) + local + typeset -A + errexit/nounset adalah senjata utama untuk skrip production-grade.
if [[ -f "$config" ]]; then
print "Config ditemukan"
elif [[ -d "$config" ]]; then
print "Config adalah direktori"
else
print "Config tidak ada"
fifor file in *.md; do
print "Memproses $file"
done
# range numerik
for i in {1..5}; do
print "Iterasi $i"
done
# C-style loop (mirip C/Python)
for ((i = 0; i < 10; i++)); do
print $i
donePerhatikan {1..5} — brace expansion Zsh (episode 5) yang langsung memberi daftar angka.
count=0
while (( count < 3 )); do
print "count=$count"
(( count++ ))
done
until [[ -f /tmp/ready ]]; do
print "Menunggu /tmp/ready..."
sleep 1
donecase "$action" in
start) systemctl start "$service" ;;
stop) systemctl stop "$service" ;;
restart) systemctl restart "$service" ;;
*) print "Aksi tidak dikenal: $action" >&2; return 1 ;;
esacZsh (seperti bash) mendukung [[ ]] — test dengan operator yang lebih kaya daripada [ ]:
[[ -f file ]] # file ada dan berupa file biasa
[[ -d dir ]] # direktori
[[ -z "$var" ]] # string kosong
[[ -n "$var" ]] # string tidak kosong
[[ $a == b* ]] # pattern match
[[ $a =~ ^https?:// ]] # regex
[[ $num -gt 10 ]] # numerik lebih besar
[[ -e "$p" && -r "$p" ]] # ada DAN bisa dibacaNote
Jangan pernah pakai [ ] (single bracket) di skrip baru — ia melebar ke beberapa argumen dan bisa gagal saat variabel mengandung spasi. [[ ]] menangani ini dengan benar karena bukan perintah eksternal, melainkan konstruksi bahasa.
(( )) mengevaluasi ekspresi aritmatika dan mengembalikan status berdasarkan hasil (non-zero = sukses):
(( result = 2 + 3 ))
print $result # 5
if (( 10 > 5 )); then
print "10 lebih besar dari 5"
fi
(( count++ )) # increment
(( count += 2 ))Perlu diingat: di Zsh, (( )) dan $(( )) menggunakan integer arithmetic. Untuk float, ada modul zsh/mathfunc — kita bahas di episode 15.
Salah satu aturan emas arsitektur skrip: setiap variabel di dalam function harus local. Tanpa local, variabel menjadi global dan "bocor" — fungsi lain atau kode setelahnya bisa tidak sengaja membaca atau menimpa nilai lama.
process_file() {
tmp="/tmp/$$.tmp" # BUG: tmp jadi global!
print "memproses" > "$tmp"
}
# Di tempat lain, $tmp masih ada — bisa menimpa file tak sengaja
print "menulis $tmp" # anehprocess_file() {
local tmp="/tmp/$$.tmp"
print "memproses" > "$tmp"
}Aturan tambahan:
process_file() {
local tmpdir file
local -i count=0 # local + integer
local -a items=() # local + array
tmpdir="${TMPDIR:-/tmp}"
...
}Untuk pemetaan key-value, Zsh punya associative array — diaktifkan dengan typeset -A:
typeset -A services
services[web]="nginx"
services[db]="postgresql"
services[cache]="redis"
print $services[web] # nginx
# iterasi semua key
for key in ${(k)services}; do
print "$key → $services[$key]"
done
# iterasi nilai saja
for value in ${(v)services}; do
print $value
done${(k)services} mengambil keys, ${(v)services} mengambil values — flag modifier khas Zsh. Associative array sangat berguna untuk konfigurasi berbasis nama (map service ke port, path, dll).
Dua opsi yang mengubah cara skrip merespons error:
Dengan errexit, skrip berhenti saat sebuah perintah gagal (exit code non-zero):
set -e # alias: setopt errexit
rm -rf "$build"
cp -r src "$build" # jika gagal, skrip berhenti di siniDengan nounset, penggunaan variabel yang belum didefinisikan menjadi error:
set -u # alias: setopt nounset
print "$TIDAK_ADA" # error: unbound variableWarning
errexit punya jebakan terkenal: perintah dalam kondisi (seperti if cmd; then) tidak memicu exit, dan perintah yang berakhir dengan || true juga dianggap aman. Uji setiap skrip dengan errexit di lingkungan staging dulu — kegagalan paling sering muncul saat ada perintah "diam-diam gagal" di pipeline.
Kombinasi standar di baris awal skrip production:
#!/usr/bin/env zsh
set -euo pipefail
LOG_DIR="${HOME}/logs"
mkdir -p "$LOG_DIR"
main() {
local build_dir="${1:?path build wajib diisi}"
if [[ ! -d "$build_dir" ]]; then
print "direktori tidak ada: $build_dir" >&2
return 1
fi
rsync -a --delete "$build_dir"/ "$LOG_DIR"/build/
print "Build selesai"
}
main "$@"Tiga detail penting dari contoh di atas:
set -euo pipefail — gabungan aman: stop saat error, error saat unbound var, error pipeline.${1:?path build wajib diisi} — parameter expansion yang error dengan pesan jika argumen kosong.main "$@" — seluruh logika di dalam satu function main, dipanggil di bagian akhir. Ini membuat variabel lokal dan aliran jelas.Pola yang dianjurkan untuk skrip di atas ~30 baris:
#!/usr/bin/env zsh
set -euo pipefail
# 1. Konstanta
readonly APP_NAME="deploy"
# 2. Helper (fungsi kecil)
log() { print "[$APP_NAME] $*"; }
# 3. Fungsi utama tiap tahap
build() { ... }
test_app() { ... }
deploy() { ... }
# 4. main — orchestrasi
main() { build && test_app && deploy; }
main "$@"local di function; gunakan typeset untuk kejelasan.errexit, perintah gagal diteruskan dan skrip "berhasil" padahal tidak.[[ ]] vs [ ] — single bracket rapuh; selalu double bracket.typeset -A — services[web]=x akan dianggap array biasa dan error.Inti yang harus dibawa pulang:
if/case/for/while/until + [[ ]] + (( )).local di semua function untuk mencegah kebocoran variabel.typeset -A untuk associative array; flag ${(k)}/${(v)} untuk iterasi.set -euo pipefail + pola main "$@".Di episode 12 selanjutnya kita akan membahas Oh My Zsh & plugin managers — menginstall Oh My Zsh, update dengan omz update, tema seperti powerlevel10k, plugin populer (git, z, sudo, autosuggestions), lalu membandingkan dengan manager modern seperti zinit yang melakukan lazy-loading demi startup cepat. Sampai jumpa di episode 12!