Kepala Skrip yang Wajib
#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t'
| Opsi | Efek |
|---|---|
-e | Berhenti begitu ada perintah yang gagal |
-u | Error bila memakai variabel yang belum diset |
-o pipefail | Pipeline dianggap gagal bila bagian mana pun gagal |
#!/bin/bash
cd /srv/aplikasi # ← bila direktori ini tidak ada, cd gagal
rm -rf ./data/* # ← tapi skrip TETAP LANJUT, menghapus di direktori
# tempat skrip dijalankan. Bisa jadi $HOME.
Ini bukan skenario teoretis — pola persis ini pernah menghapus data produksi di banyak organisasi. set -e menghentikan skrip pada baris cd, dan -u menangkap variabel kosong yang menjadi penyebab umum lainnya.
Tanda Kutip: Sumber Bug Nomor Satu
# ❌ Nama berkas berspasi akan pecah menjadi beberapa argumen
for f in $(ls); do rm $f; done
# ✅
for f in *; do rm -- "$f"; done
# ❌ Variabel kosong membuat perintah menjadi berbahaya
rm -rf $DIR/* # bila DIR kosong → rm -rf /*
# ✅ Selalu kutip, dan beri nilai bawaan
rm -rf "${DIR:?DIR wajib diisi}"/*
Kutip setiap ekspansi variabel kecuali Anda punya alasan spesifik untuk tidak melakukannya. "$var", "$@", "${arr[@]}". Sintaks ${VAR:?pesan} membuat skrip berhenti dengan pesan jelas bila variabel kosong — jauh lebih baik daripada menjalankan perintah destruktif dengan path kosong.
Kerangka Skrip yang Baik
#!/usr/bin/env bash
set -euo pipefail
readonly SKRIP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_TAG="pencadangan"
log() { printf '[%s] %s\n' "$(date -Is)" "$*" >&2; }
gagal() { log "GAGAL: $*"; exit 1; }
bersihkan() {
local kode=$?
[[ -n "${TMPDIR_SAYA:-}" ]] && rm -rf "$TMPDIR_SAYA"
exit "$kode"
}
trap bersihkan EXIT
periksa_syarat() {
command -v pg_dump >/dev/null || gagal "pg_dump tidak ditemukan"
[[ -n "${DATABASE_URL:-}" ]] || gagal "DATABASE_URL belum diset"
}
utama() {
periksa_syarat
TMPDIR_SAYA="$(mktemp -d)"
log "mulai mencadangkan"
pg_dump "$DATABASE_URL" > "$TMPDIR_SAYA/dump.sql" \
|| gagal "pg_dump gagal"
log "selesai: $(du -h "$TMPDIR_SAYA/dump.sql" | cut -f1)"
}
utama "$@"
trap ... EXIT menjamin pembersihanFungsi yang dipasang lewat trap EXIT berjalan apa pun cara skrip berakhir — selesai normal, gagal karena set -e, atau dihentikan sinyal. Ini cara paling andal memastikan direktori sementara terhapus dan kunci dilepas.
Perbandingan & Kondisi
# Gunakan [[ ]], bukan [ ]
[[ -f "$berkas" ]] # berkas ada
[[ -d "$dir" ]] # direktori ada
[[ -z "$var" ]] # kosong
[[ -n "$var" ]] # tidak kosong
[[ "$a" == "$b" ]] # perbandingan teks
[[ "$a" =~ ^[0-9]+$ ]] # regex
(( angka > 10 )) # aritmetika
# Nilai bawaan
nama="${1:-bawaan}"
: "${WAJIB:?variabel ini harus diisi}"
Menjalankan Sesuatu Sekali Saja
# Mencegah dua salinan skrip berjalan bersamaan
exec 9>/var/lock/pencadangan.lock
flock -n 9 || { echo "sudah berjalan"; exit 0; }
# Alternatif
flock -n /var/lock/tugas.lock /usr/local/bin/tugas.sh
Sebuah tugas yang biasanya selesai 3 menit tetapi suatu hari melambat menjadi 20 menit akan berjalan bersamaan dengan jadwal berikutnya. Bila tugas itu menyentuh basis data atau berkas yang sama, hasilnya bisa rusak. Penguncian berkas adalah pengaman yang murah dan hampir selalu layak dipasang.
ShellCheck
shellcheck skrip.sh
ShellCheck menangkap variabel tak berkutip, perbandingan yang keliru, dan kesalahan kuoting sebelum skrip pernah dijalankan. Menjadikannya bagian pipeline CI mencegah seluruh kelas bug yang biasanya baru ditemukan saat skrip berjalan di produksi pada jam dua pagi.