Belajar BASH Scripting - Error Handling & Robustness (Unofficial BASH Strict Mode)
Episode 18 of 27

Belajar BASH Scripting - Error Handling & Robustness (Unofficial BASH Strict Mode)

Skrip yang berjalan mulus di laptop bisa runtuh di produksi. Episode ini membahas exit codes, operator kondisi && / ||, hingga mengaktifkan set -e, set -u, dan set -o pipefail — serta jebakan set -e saat grep gagal atau perintah dipakai di dalam kondisi. Diakhiri praktik memperkuat skrip lapis demi lapis.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Pendahuluan

Setelah di episode 17 sebelumnya kita membahas Command Substitution, Process Substitution & HereDoc — cara mengambil output perintah, membandingkan stream tanpa file sementara, dan mencetak template yang rapi — kalian kini bisa menulis skrip yang "berbicara" dengan sistem. Namun ada kenyataan pahit yang menunggu setiap skrip yang dipindahkan dari laptop ke server produksi: skrip yang tidak menangani kegagalan adalah bom waktu.

Bayangkan kalian sedang mengemudikan mobil. Sepanjang jalan mulus di kota, kalian tidak perlu peduli pada rem — mobil tetap jalan. Masalahnya, jalan produksi bukan jalan kota: ada lampu merah mendadak (perintah tidak ditemukan), tanah longsor (disk penuh), dan pengemudi lain yang memotong jalur (proses lain merebut resource). Mobil tanpa rem yang berfungsi di jalan seperti itu tidak sekadar tidak nyaman — ia berbahaya. Error handling adalah sistem rem skrip kalian. Tanpa itu, skrip yang gagal setengah jalan akan terus melanjutkan sisa instruksinya dengan asumsi yang sudah salah — hasilnya jauh lebih buruk daripada sekadar "gagal".

Kabar baiknya, BASH menyediakan satu rangkaian pengaturan yang oleh komunitas disebut Unofficial BASH Strict Mode: set -euo pipefail. Ketiga huruf itu menutup tiga celah paling umum yang membuat skrip berjalan diam-diam salah. Namun — dan ini penting — strict mode bukan saklar ajaib yang membuat skrip kebal. Justru sebaliknya: strict mode membuat kegagalan menjadi nyaring. Setiap perintah yang gagal akan menghentikan skrip, sehingga kalian tahu persis di mana dan kapan harus memperbaiki.

Di episode ini kita akan membedah: apa itu exit codes dan $?, bagaimana &&/|| menjadi "jantung" keputusan di BASH, apa saja yang dilakukan set -e, set -u, dan set -o pipefail, kapan harus mengizinkan kegagalan dengan sengaja (cmd || true, if cmd; then), serta praktik memperkuat sebuah skrip langkah demi langkah dengan penanda diff. Siapkan sabuk pengaman — kita mulai.

Pembahasan Utama

Exit Codes: Bahasa Status yang Dimengerti Semua Proses

Setiap perintah yang kalian jalankan di terminal — dan setiap perintah di dalam skrip — mengembalikan sebuah exit code ketika selesai. Bayangkan seperti stempel kelulusan di akhir rapat: 0 berarti "semua beres, sesuai rencana", dan angka lain berarti "ada yang tidak beres". Aturan baku POSIX:

Exit CodeMaknaContoh
0Suksesgrep menemukan baris yang cocok
1Gagal (umum)grep tidak menemukan apa pun, sintaks salah
2Penggunaan salahPerintah dipanggil dengan argumen keliru
126Ditemukan tapi tidak bisa dieksekusiFile bukan binary yang valid
127Perintah tidak ditemukancommand not found
130Dihentikan oleh SIGINTSkrip di-Ctrl+C
255Exit code yang di-wrap ke 0-255exit -1 berubah jadi 255

Perhatikan: BASH hanya menyimpan exit code dari perintah terakhir yang dijalankan. Nilainya tersedia di variabel khusus $? — tetapi hanya sekali. Segera setelah kalian menjalankan perintah lain (bahkan echo), nilai $? yang lama tertimpa:

Membaca exit code dengan $?
grep -q "ERROR" app.log
echo "exit grep  : $?"
grep -q "tidakada" app.log
echo "exit grep  : $?"
echo "Hello" | grep -q "Hello"
echo "exit pipe : $?"
Contoh output
exit grep  : 0
exit grep  : 1
exit pipe  : 0

grep -q mengembalikan 0 jika ditemukan dan 1 jika tidak — inilah alasan grep sering dipakai dalam kondisi if. Kalian juga bisa menghentikan skrip dengan exit code yang kalian tentukan sendiri menggunakan exit <code>:

Mengatur exit code sendiri
#!/bin/bash
 
if [[ $# -ne 1 ]]; then
    echo "Usage: $0 <nama>" >&2
    exit 2
fi
 
echo "Halo, $1!"
exit 0

Mengapa peduli dengan exit code? Karena skrip kalian sendiri bisa dipanggil oleh skrip lain — oleh cron job, oleh pipeline CI/CD, atau oleh supervisor process. Mereka semua membaca exit code skrip kalian untuk memutuskan langkah berikutnya. exit 0 vs exit 1 di skrip kalian adalah cara kalian berkomunikasi dengan seluruh ekosistem di sekitarnya. Skrip yang selalu mengembalikan 0 meskipun gagal adalah kebohongan yang berbahaya bagi otomasi yang mempercayainya.

&& dan ||: Dua Kata Kunci Keputusan

BASH menawarkan cara ringkas untuk menjalankan perintah kedua hanya berdasarkan hasil perintah pertama:

  • perintah1 && perintah2 — jalankan perintah2 hanya jika perintah1 sukses (exit 0).
  • perintah1 || perintah2 — jalankan perintah2 hanya jika perintah1 gagal (exit bukan 0).

Analoginya: && seperti urutan kerja yang berantai ("kalau tiket berhasil dibeli, baru pesan hotel"), sedangkan || seperti rencana cadangan ("kalau server utama mati, pakai server cadangan"). Contoh nyata:

&& dan || dalam satu baris
mkdir -p /backup && echo "Folder backup siap" || echo "Gagal membuat folder"
cp app.conf /backup/app.conf.bak && echo "Backup berhasil" || echo "Backup gagal"

Baris pertama dibaca: "buat folder backup; kalau berhasil, beri tahu; kalau gagal, beri tahu dengan pesan berbeda". Ini adalah salah satu pola paling produktif di BASH — seluruh alur logika sederhana bisa diungkapkan tanpa blok if.

Strict Mode: set -euo pipefail

Sekarang inti episode. Ada empat pengaturan set yang mengubah perilaku skrip, dan tiga di antaranya membentuk paket standar industri. Mari bedah satu per satu — dan yang lebih penting, mengapa masing-masing ada:

OpsiEfekMengapa penting
set -eKeluar segera saat sebuah perintah mengembalikan exit code bukan 0Mencegah skrip terus berjalan dengan asumsi yang sudah salah
set -uKeluar saat mengakses variabel yang belum disetelMencegah $VAR kosong yang diam-diam merusak logika
set -o pipefailExit code pipeline = exit code perintah yang gagal paling "kanan"Mencegah pipeline "sukses" padahal salah satu tahapnya gagal
set -xMencetak setiap perintah sebelum dieksekusiMode trace untuk debugging (dibahas penuh di episode 20)

Ketiganya digabung menjadi satu baris yang bisa kalian pakai di hampir semua skrip:

Unofficial BASH Strict Mode
#!/bin/bash
set -euo pipefail

set -e adalah "sistem rem" pertama. Tanpa set -e, perhatikan apa yang terjadi pada skrip berikut yang dijalankan saat file config.yaml tidak ada:

Tanpa set -e: kegagalan diabaikan
#!/bin/bash
 
cp config.yaml /backup/config.yaml
echo "Backup selesai!"   # baris ini TETAP jalan walau cp gagal

cp gagal dengan exit 1, tetapi skrip tetap melanjutkan ke echo — dan user menerima pesan "Backup selesai!" yang menyesatkan. Dengan set -e, baris echo tidak akan pernah tercapai; skrip berhenti tepat di cp, dan exit code skrip menjadi status kegagalan cp. Lebih jujur, lebih aman.

set -u menutup celah kedua. Tanpa set -u, mengakses variabel yang tidak pernah disetel menghasilkan string kosong yang senyap:

Tanpa set -u: variabel tak terdefinisi = kosong
#!/bin/bash
 
echo "Key: ${API_KEY}"
# output: "Key: " — tanpa peringatan apa pun

${API_KEY} yang kosong tidak memunculkan error. Skrip terus berjalan, mengirim request autentikasi dengan key kosong, dan baru gagal jauh di kemudian hari di tempat yang sulit dilacak. Dengan set -u, BASH langsung berhenti dengan unbound variable — lokasi bug teridentifikasi seketika. Ini seperti memiliki "lampu indikator bahan bakar" di dashboard, bukan menunggu mesin mati di tengah jalan tol.

set -o pipefail menutup celah ketiga, yang paling halus. Bayangkan pipeline:

Mengapa pipefail dibutuhkan
cat access.log | grep "404" | wc -l

Tanpa pipefail, exit code pipeline = exit code perintah terakhir (wc), yang selalu 0 asalkan berhasil menghitung baris — meskipun cat access.log gagal (file tidak ada)! Pipeline mengembalikan 0, padahal datanya kosong. Dengan pipefail, jika salah satu tahap gagal, seluruh pipeline dianggap gagal dengan exit code tahap yang gagal. Ini mencegah skrip membangun keputusan di atas data yang tidak pernah ada.

Important

set -euo pipefail membuat kegagalan menjadi eksplisit dan keras — bukan membuat skrip kebal. Filosofinya: lebih baik skrip berhenti dengan suara berisik dan exit code jelas, daripada melanjutkan dengan data yang salah lalu gagal diam-diam di tempat yang jauh. Di tim production, "gagal cepat dan keras" selalu lebih baik daripada "gagal lambat dan misterius".

Kapan Strict Mode Membunuh Skrip yang Sebenarnya Baik-Baik Saja

set -e punya reputasi kontroversial karena ada satu jebakan terkenal: perintah yang "sengaja" mengembalikan non-zero akan menghentikan skrip — dan ada beberapa kasus di mana kegagalan itu justru bagian normal dari alur. Kasus paling klasik adalah grep yang tidak menemukan kecocokan, atau diff antara dua file yang sama.

Untungnya, BASH sudah memperhitungkan ini: perintah di dalam kondisi if, while, atau setelah &&/|| tidak memicu set -e — karena di situ exit code memang sedang diperiksa, bukan diabaikan. Inilah pola yang benar:

Pola aman untuk perintah yang boleh gagal
# 1) Tempatkan di dalam kondisi
if grep -q "ERROR" app.log; then
    echo "Ditemukan error di log!"
fi
 
# 2) Padukan dengan || true
grep -q "DEPLOYED" status.txt || true
 
# 3) Gunakan || untuk lari ke jalur alternatif
ping -c 1 10.0.0.1 >/dev/null 2>&1 || echo "Host 10.0.0.1 tidak terjangkau"

Baris grep -q ... || true memberitahu BASH: "saya tahu perintah ini bisa gagal, dan saya menganggap kegagalannya tidak masalah." Ini adalah persetujuan eksplisit — bedanya dengan sekadar tidak memakai set -e, yang mengabaikan semua kegagalan tanpa persetujuan apa pun.

Warning

set -e tidak menangkap semua kegagalan. Ada celah terkenal yang wajib kalian hafal: (1) perintah yang gagal di dalam subshell dari command substitution tidak selalu menghentikan skrip induk, (2) set -e tidak aktif dalam fungsi yang dipanggil dari kondisi, dan (3) pipeline tanpa pipefail tetap menyembunyikan kegagalan tahap awal. Jangan pernah menganggap set -e sebagai pengganti validasi eksplisit — ia adalah pengaman tambahan, bukan jaring pengaman tunggal.

Praktik Nyata: Memperkuat Skrip Lapis Demi Lapis

Sekarang mari lihat bagaimana sebuah skrip biasa berubah menjadi skrip yang tangguh. Skrip ini mem-backup database dengan pg_dump. Ini sebelum hardening — penuh lubang:

backup_db.sh — sebelum hardening
#!/bin/bash
 
out="/backup/db_$(date +%F).sql"
pg_dump myapp > "$out"
gzip "$out"
echo "Backup selesai: $out.gz"

Masalah yang bersembunyi: pg_dump bisa gagal (kredensial salah, DB down) dan skrip tetap menulis file kosong lalu mengompresnya; gzip yang gagal tidak akan pernah ketahuan; dan variabel out bisa kosong jika date bermasalah. Sekarang mari perkuat dengan diff:

backup_db.sh — setelah hardening
#!/bin/bash
set -euo pipefail
out="/backup/db_$(date +%F).sql"
BACKUP_DIR="/backup"
mkdir -p "$BACKUP_DIR"
pg_dump myapp > "$out" \
    || { echo "pg_dump gagal!" >&2; exit 1; }
test -s "$out" \
    || { echo "File backup kosong — ada yang salah" >&2; exit 1; }
gzip "$out"
echo "Backup selesai: $out.gz"

Perubahan yang kita lakukan dan alasannya:

  1. set -euo pipefail — fondasi: berhenti di kegagalan, variabel tak terdefinisi = error, pipeline yang gagal dilaporkan.
  2. mkdir -p "$BACKUP_DIR" — memastikan direktori tujuan ada; -p tidak error jika sudah ada.
  3. pg_dump ... || { ...; exit 1; } — jika dump gagal, kita sengaja menangkap dan mencetak pesan yang jelas ke stderr (>&2) sebelum berhenti. Jangan biarkan file kosong dibuat.
  4. test -s "$out" — memverifikasi file dump tidak kosong sebelum dikompres. -s mengembalikan true jika file berukuran lebih dari nol byte.
  5. Pesan error ke stderr (>&2) — praktik baik: pesan masalah tidak boleh bercampur dengan output normal, agar pipeline yang membaca stdout skrip ini tidak terkontaminasi.

Sekarang, apakah test -s dengan || diikuti exit 1 membatalkan manfaat set -e? Tidak — justru sebaliknya: kita memilih untuk menangani kegagalan ini secara eksplisit karena pesan yang kita berikan lebih informatif daripada pesan set -e. Keduanya bekerja sama: set -e menangkap apa yang tidak kita prediksi, dan || menangkap apa yang kita prediksi dan ingin beri pesan khusus.

Tip

Gunakan set -euo pipefail sebagai baris kedua setiap skrip baru kalian — persis seperti menempel seatbelt sebelum menyalakan mesin. Di akhir series kita akan membahas ShellCheck, dan kalian akan melihat bahwa banyak peringatan yang dikeluarkannya berkaitan dengan pola yang kita bahas di episode ini: variabel tak ter-quote, perintah yang bisa gagal tanpa penanganan, dan penggunaan || true yang membabi buta.

Jebakan Klasik (Common Pitfalls)

1. set -e + grep yang tidak cocok = skrip mati. Ini jebakan paling umum bagi pendatang baru. grep -q "pattern" file mengembalikan 1 jika tidak ada kecocokan, dan dengan set -e skrip langsung berhenti — padahal "tidak ada kecocokan" bisa jadi justru kondisi yang kalian tunggu. Solusi: bungkus dalam if, atau tambahkan || true jika kegagalannya memang tidak relevan.

2. Lupa set -u → variabel kosong yang senyap. Tanpa set -u, $UNDEFINED_VAR menjadi string kosong, dan skrip bisa terus berjalan sambil menghapus file yang path-nya sebagian dari variabel kosong. set -u mengubah hal ini menjadi error langsung yang bisa dilacak.

3. pipefail "menyalakan lampu" di pipeline yang seharusnya mengizinkan kegagalan. Setelah mengaktifkan pipefail, perhatikan semua pipeline: cmd1 | cmd2 akan gagal jika cmd1 gagal. Beberapa pipeline lama (misalnya yes | head) memang sengaja membuat perintah kiri berhenti dengan SIGPIPE — pahami apakah itu diterima atau perlu || true.

4. set -e + cmd || exit 1 ganda. Menulis cmd || exit 1 di dalam skrip yang sudah memakai set -e tidak berbahaya, tetapi sering redundan. Yang berbahaya adalah menulis cmd || { ... } lalu lupa exit di dalam blok — skrip melanjutkan dengan asumsi yang salah. Selalu akhiri blok penanganan dengan exit <code>.

5. Menguji hanya di lingkungan mulus. Skrip yang diuji di laptop dengan semua perintah tersedia dan disk lega akan tampak "kuat" padahal rapuh. Uji jalur kegagalannya: jalankan dengan file yang tidak ada, dengan izin ditolak, dengan disk penuh. Strict mode memastikan kegagalan terlihat — tugas kalian menyediakan lingkungan uji di mana kegagalan itu benar-benar terjadi.

Penutup

Di episode 18 ini kalian telah mengubah skrip dari "asumsi semuanya berjalan" menjadi "menuntut bukti". Kalian memahami exit codes sebagai bahasa status universal (0 sukses, 1-255 gagal, $? untuk membaca nilai terakhir), &&/|| sebagai kata kunci keputusan satu baris, dan Unofficial BASH Strict Mode set -euo pipefail sebagai pengaman tiga lapis: berhenti di kegagalan, menolak variabel tak terdefinisi, dan melaporkan pipeline yang gagal. Kalian juga belajar kapan sengaja mengizinkan kegagalan dengan cmd || true dan if cmd; then, plus memverifikasi hasil (test -s) sebelum melanjutkan.

Poin kunci yang harus kalian bawa pulang:

  • Exit code adalah cara skrip berkomunikasi dengan cron, CI/CD, dan supervisor — jangan pernah berbohong dengan selalu exit 0.
  • set -euo pipefail di baris kedua setiap skrip: kegagalan menjadi nyaring, bukan diam-diam.
  • Perintah dalam if, while, atau setelah || tidak memicu set -e — manfaatkan untuk perintah yang memang boleh gagal.
  • set -e bukan jaring pengaman tunggal; validasi hasil tetap wajib.

Sekarang skrip kalian tahu kapan harus berhenti. Tapi ada pertanyaan berikutnya yang lebih mendasar: ketika skrip berhenti, siapa yang membersihkan? File sementara yang dibuat, lock yang dipegang, direktori yang setengah jadi — semuanya bisa tertinggal saat skrip mati mendadak. Di episode 19 selanjutnya kita akan menjawabnya dengan topik Signals Handling & Traps (Clean-up Operations): bagaimana menangkap sinyal seperti SIGINT dan SIGTERM, memasang trap untuk membersihkan saat keluar, dan memastikan skrip kalian tidak pernah meninggalkan kekacauan di belakang. Sampai jumpa!

Belajar BASH Scripting - Error Handling & Robustness (Unofficial BASH Strict Mode) | Belajar BASH Scripting