Belajar Zsh - Scripting: Control Flow & Arsitektur
Series/Belajar Zsh/Episode 11
Episode 11 of 23

Belajar Zsh - Scripting: Control Flow & Arsitektur

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

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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.

Control Flow: Dasar

if / elif / else

if / elif / else
if [[ -f "$config" ]]; then
    print "Config ditemukan"
elif [[ -d "$config" ]]; then
    print "Config adalah direktori"
else
    print "Config tidak ada"
fi

for (loop atas daftar)

for loop
for 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
done

Perhatikan {1..5} — brace expansion Zsh (episode 5) yang langsung memberi daftar angka.

while dan until

while / until
count=0
while (( count < 3 )); do
    print "count=$count"
    (( count++ ))
done
 
until [[ -f /tmp/ready ]]; do
    print "Menunggu /tmp/ready..."
    sleep 1
done

case

case
case "$action" in
    start) systemctl start "$service" ;;
    stop)  systemctl stop "$service" ;;
    restart) systemctl restart "$service" ;;
    *) print "Aksi tidak dikenal: $action" >&2; return 1 ;;
esac

Test dengan Double Bracket [[ ]]

Zsh (seperti bash) mendukung [[ ]] — test dengan operator yang lebih kaya daripada [ ]:

Test [[ ]]
[[ -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 dibaca

Note

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.

Arithmetic dengan (( ))

(( )) mengevaluasi ekspresi aritmatika dan mengembalikan status berdasarkan hasil (non-zero = sukses):

Arithmetic (( ))
(( 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.

Variabel local: Wajib di Dalam Fungsi

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.

Variabel bocor
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"     # aneh
Dengan local
process_file() {
    local tmp="/tmp/$$.tmp"
    print "memproses" > "$tmp"
}

Aturan tambahan:

Deklarasi local
process_file() {
    local tmpdir file
    local -i count=0        # local + integer
    local -a items=()       # local + array
    tmpdir="${TMPDIR:-/tmp}"
    ...
}

Associative Array: typeset -A

Untuk pemetaan key-value, Zsh punya associative array — diaktifkan dengan typeset -A:

Associative array
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).

Opsi Keamanan: errexit dan nounset

Dua opsi yang mengubah cara skrip merespons error:

errexit (set -e)

Dengan errexit, skrip berhenti saat sebuah perintah gagal (exit code non-zero):

errexit
set -e                # alias: setopt errexit
rm -rf "$build"
cp -r src "$build"    # jika gagal, skrip berhenti di sini

nounset (set -u)

Dengan nounset, penggunaan variabel yang belum didefinisikan menjadi error:

nounset
set -u                # alias: setopt nounset
print "$TIDAK_ADA"    # error: unbound variable

Warning

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:

script.zsh (header aman)
#!/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:

  1. set -euo pipefail — gabungan aman: stop saat error, error saat unbound var, error pipeline.
  2. ${1:?path build wajib diisi} — parameter expansion yang error dengan pesan jika argumen kosong.
  3. Pola main "$@" — seluruh logika di dalam satu function main, dipanggil di bagian akhir. Ini membuat variabel lokal dan aliran jelas.

Arsitektur Skrip yang Baik

Pola yang dianjurkan untuk skrip di atas ~30 baris:

Pola struktur skrip
#!/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 "$@"

Common Pitfalls

  • Global leak — variabel tanpa local di function; gunakan typeset untuk kejelasan.
  • Melewatkan guard — tanpa errexit, perintah gagal diteruskan dan skrip "berhasil" padahal tidak.
  • [[ ]] vs [ ] — single bracket rapuh; selalu double bracket.
  • Associative array tanpa typeset -Aservices[web]=x akan dianggap array biasa dan error.

Penutup

Inti yang harus dibawa pulang:

  • Control flow Zsh lengkap: if/case/for/while/until + [[ ]] + (( )).
  • local di semua function untuk mencegah kebocoran variabel.
  • typeset -A untuk associative array; flag ${(k)}/${(v)} untuk iterasi.
  • Header skrip aman: 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!

Belajar Zsh - Scripting: Control Flow & Arsitektur | Belajar Zsh