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.

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.
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 Code | Makna | Contoh |
|---|---|---|
0 | Sukses | grep menemukan baris yang cocok |
1 | Gagal (umum) | grep tidak menemukan apa pun, sintaks salah |
2 | Penggunaan salah | Perintah dipanggil dengan argumen keliru |
126 | Ditemukan tapi tidak bisa dieksekusi | File bukan binary yang valid |
127 | Perintah tidak ditemukan | command not found |
130 | Dihentikan oleh SIGINT | Skrip di-Ctrl+C |
255 | Exit code yang di-wrap ke 0-255 | exit -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:
grep -q "ERROR" app.log
echo "exit grep : $?"
grep -q "tidakada" app.log
echo "exit grep : $?"
echo "Hello" | grep -q "Hello"
echo "exit pipe : $?"exit grep : 0
exit grep : 1
exit pipe : 0grep -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>:
#!/bin/bash
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <nama>" >&2
exit 2
fi
echo "Halo, $1!"
exit 0Mengapa 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 KeputusanBASH 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:
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.
set -euo pipefailSekarang 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:
| Opsi | Efek | Mengapa penting |
|---|---|---|
set -e | Keluar segera saat sebuah perintah mengembalikan exit code bukan 0 | Mencegah skrip terus berjalan dengan asumsi yang sudah salah |
set -u | Keluar saat mengakses variabel yang belum disetel | Mencegah $VAR kosong yang diam-diam merusak logika |
set -o pipefail | Exit code pipeline = exit code perintah yang gagal paling "kanan" | Mencegah pipeline "sukses" padahal salah satu tahapnya gagal |
set -x | Mencetak setiap perintah sebelum dieksekusi | Mode trace untuk debugging (dibahas penuh di episode 20) |
Ketiganya digabung menjadi satu baris yang bisa kalian pakai di hampir semua skrip:
#!/bin/bash
set -euo pipefailset -e adalah "sistem rem" pertama. Tanpa set -e, perhatikan apa yang terjadi pada skrip berikut yang dijalankan saat file config.yaml tidak ada:
#!/bin/bash
cp config.yaml /backup/config.yaml
echo "Backup selesai!" # baris ini TETAP jalan walau cp gagalcp 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:
#!/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:
cat access.log | grep "404" | wc -lTanpa 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".
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:
# 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.
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:
#!/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:
#!/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:
set -euo pipefail — fondasi: berhenti di kegagalan, variabel tak terdefinisi = error, pipeline yang gagal dilaporkan.mkdir -p "$BACKUP_DIR" — memastikan direktori tujuan ada; -p tidak error jika sudah ada.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.test -s "$out" — memverifikasi file dump tidak kosong sebelum dikompres. -s mengembalikan true jika file berukuran lebih dari nol byte.>&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.
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.
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 0.set -euo pipefail di baris kedua setiap skrip: kegagalan menjadi nyaring, bukan diam-diam.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!