Bab 9 sampai 11 fokus ke tool pemrosesan teks seperti grep, sed, dan awk, tapi Bab 12 ini mengajak kita kembali ke jantung Bash scripting itu sendiri: cara sebuah script menerima instruksi dari luar lewat argumen yang diketik di baris perintah. Mekanisme dasarnya, yaitu $1, $2, $@, dan $#, sebenarnya sudah disinggung di Bab 8.2 saat membahas argumen fungsi. Bedanya, di level script argumen ini menjadi pintu masuk utama User, Developer, maupun Sysadmin berinteraksi dengan script yang kita tulis, entah dijalankan langsung dari terminal, dipanggil dari script lain, atau dijadwalkan lewat cron yang akan dibahas di Bab 18. Script yang cuma bisa dijalankan dengan satu cara tetap tanpa opsi apa pun cepat terasa kaku begitu kebutuhannya berkembang, sementara script yang mendukung flag seperti -v atau --verbose jauh lebih fleksibel dan terasa seperti tool baku di sistem Linux pada umumnya. Bab ini membahas tuntas tiga lapis kemampuan menangani argumen: positional parameter mentah beserta perintah shift, parsing opsi terstruktur lewat getopts, penanganan opsi panjang gaya --verbose yang tidak didukung getopts, dan ditutup studi kasus membangun satu CLI tool sederhana yang menggabungkan semuanya.
12.1 Positional Parameter dan Shift
Sub-bab ini membahas argumen script dalam bentuk paling mentah, yaitu positional parameter yang otomatis tersedia begitu script dijalankan, beserta perintah shift untuk menggeser posisinya satu per satu.
12.1.1 $0 dan Positional Parameter Script
Begitu sebuah script dijalankan dengan argumen, Bash otomatis menyimpan tiap argumen itu ke variabel bernomor sesuai posisinya, disebut positional parameter, persis konsep yang sama seperti argumen fungsi di Bab 8.2.1, hanya saja sekarang cakupannya adalah seluruh script, bukan satu fungsi saja.
#!/bin/bash
echo "Nama script: $0"
echo "Argumen pertama: $1"
echo "Argumen kedua: $2"Menjalankan script di atas dengan ./info.sh nginx restart menampilkan Argumen pertama: nginx dan Argumen kedua: restart. Variabel $0 sendiri bukan argumen, melainkan nama script itu sendiri, persis seperti yang dituliskan saat memanggilnya. Detail yang wajib dipahami: isi $0 mengikuti persis cara script dipanggil, bukan selalu nama file polos. Menjalankan ./info.sh membuat $0 berisi ./info.sh, sementara menjalankan lewat path lengkap seperti /opt/scripts/info.sh membuat $0 berisi path lengkap itu juga. Kalau yang dibutuhkan cuma nama filenya saja tanpa embel-embel path, kombinasikan dengan perintah basename yang sudah disinggung sekilas konsepnya di Bab 2.
echo "Dipanggil sebagai: $(basename "$0")"Untuk mengakses argumen kesepuluh dan seterusnya, tanda kurung kurawal wajib dipakai, ditulis ${10}, bukan $10. Tanpa kurung kurawal, Bash membaca $10 sebagai $1 yang diikuti karakter 0 biasa, bukan sebagai argumen kesepuluh, jebakan kecil yang jarang muncul di script sehari-hari tapi tetap penting diketahui kalau suatu saat script menerima argumen dalam jumlah besar.
12.1.2 $# dan $@ untuk Argumen yang Fleksibel
$# dan $@ pada level script berperilaku identik dengan versi fungsinya yang sudah dibahas tuntas di Bab 8.2.2 dan 8.2.3: $# berisi jumlah total argumen yang diterima script, sementara "$@" yang dikutip mengembang jadi tiap argumen sebagai word terpisah, cocok dipakai di dalam for loop. Aturan yang sama juga berlaku di sini: selalu kutip "$@", jangan $@ polos, supaya argumen yang mengandung spasi tidak ikut terpecah jadi beberapa item.
#!/bin/bash
echo "Total argumen: $#"
for argumen in "$@"; do
echo "Item: $argumen"
doneMenjalankan script di atas dengan ./daftar.sh "laporan januari.txt" restart tetap menampilkan tepat dua baris Item, meski argumen pertamanya mengandung spasi di dalamnya, karena "$@" menjaga setiap argumen sebagai satu kesatuan utuh.
12.1.3 Perintah shift Menggeser Posisi Argumen
Builtin shift menggeser seluruh positional parameter satu posisi ke kiri: nilai $2 berpindah jadi $1, $3 berpindah jadi $2, dan seterusnya, sementara argumen paling pertama sebelumnya dibuang begitu saja. $# ikut berkurang satu setiap kali shift dijalankan. Perintah ini sangat berguna saat script perlu memproses argumen satu per satu tanpa tahu sejak awal ada berapa banyak, pola yang jadi fondasi getopts di 12.2 dan parsing manual di 12.3.
#!/bin/bash
while [[ $# -gt 0 ]]; do
echo "Memproses: $1"
shift
doneMenjalankan script di atas dengan ./proses.sh nginx mysql redis menampilkan tiga baris Memproses, satu untuk tiap argumen, karena loop while terus berjalan selama $# masih lebih besar dari nol, dan setiap putaran memproses $1 lalu menggesernya keluar lewat shift. shift juga menerima argumen angka opsional untuk menggeser lebih dari satu posisi sekaligus, misalnya shift 2 membuang dua argumen pertama langsung, kombinasi yang nanti dipakai di 12.2.4 untuk membersihkan opsi yang sudah diproses getopts.
12.1.4 Troubleshooting: Validasi Argumen Sebelum Diproses
Kebiasaan yang sama seperti dibahas di Bab 8.2.4 untuk fungsi juga berlaku di level script: mengakses $1 tanpa memeriksa dulu apakah argumennya benar-benar dikirim tidak menghasilkan error, melainkan diam-diam berisi string kosong. Kalau script mengasumsikan argumen tertentu pasti ada, kegagalan validasi ini bisa menyebar jadi bug yang membingungkan di baris-baris berikutnya.
#!/bin/bash
if [[ $# -lt 1 ]]; then
echo "Error: script butuh minimal 1 argumen (nama service)" >&2
echo "Penggunaan: $(basename "$0") NAMA_SERVICE" >&2
exit 1
fi
systemctl is-active "$1"Pesan error dan petunjuk penggunaan di atas sengaja diarahkan ke stderr lewat >&2, bukan ke stdout biasa, konsep redirection yang sudah dibahas di Bab 5.2 dan 5.3, supaya pesan error tetap terlihat di terminal meski stdout script sedang dialihkan ke file atau dipipe ke perintah lain. Exit status 1 di baris exit 1 juga penting: script atau pemanggil lain yang memeriksa $?, konsep yang sudah dibahas di Bab 6.4.1, jadi tahu persis validasi argumen ini gagal, bukan cuma berhenti begitu saja tanpa penanda apa pun.
12.2 Parsing Opsi dengan getopts
Membaca argumen mentah lewat $1, $2, dan shift seperti di 12.1 cukup untuk script sederhana, tapi jadi merepotkan begitu script perlu mendukung opsi bergaya -v, -f nama_file, atau kombinasi keduanya sekaligus dalam urutan bebas. Builtin getopts hadir khusus untuk kebutuhan ini: mem-parsing opsi satu karakter secara terstruktur, lengkap dengan penanganan argumen opsi dan validasi opsi tak dikenal.
12.2.1 Sintaks Dasar: optstring dan OPTARG
getopts dipanggil dengan pola getopts optstring name, biasanya di dalam kondisi while supaya seluruh opsi diproses satu per satu sampai habis.
while getopts ":d:ah" opt; do
case "$opt" in
d) direktori="$OPTARG" ;;
a) tampilkan_semua=1 ;;
h) tampilkan_bantuan; exit 0 ;;
esac
doneoptstring, dalam contoh di atas berupa ":d:ah", berisi daftar karakter opsi yang dikenali script. Setiap karakter yang diikuti titik dua menandakan opsi itu wajib disertai argumen, sementara karakter tanpa titik dua berarti opsi itu cukup berdiri sendiri sebagai flag tanpa nilai tambahan, dibahas lebih lanjut di 12.2.2. Setiap kali dipanggil, getopts menaruh huruf opsi yang ditemukan ke variabel yang namanya ditentukan sendiri, dalam contoh di atas bernama opt, dan menaruh indeks argumen berikutnya yang belum diproses ke variabel bawaan OPTIND. Kalau opsi yang ditemukan butuh argumen, nilainya otomatis tersedia di variabel bawaan OPTARG. Tabel berikut merangkum variabel-variabel bawaan yang terlibat.
| Variabel | Arti |
|---|---|
OPTARG | Nilai argumen dari opsi yang baru diproses, kalau opsi itu butuh argumen |
OPTIND | Indeks argumen berikutnya yang akan diproses; diinisialisasi ke 1 setiap kali shell atau script dijalankan |
OPTERR | Kalau diset ke 0, getopts tidak pernah menampilkan pesan error apa pun, walau optstring tidak diawali titik dua |
Satu detail yang wajib diingat, terutama saat getopts dipanggil lebih dari sekali dalam satu sesi shell yang sama (misalnya di dalam script yang di-source berulang saat pengujian interaktif): Bash tidak mereset OPTIND secara otomatis di antara pemanggilan-pemanggilan itu. Kalau getopts perlu memproses set argumen baru dalam sesi shell yang sama, OPTIND=1 harus di-set ulang secara manual lebih dulu, atau parsing berikutnya bisa mulai dari posisi yang salah.
12.2.2 Opsi dengan Argumen Wajib
Sebagai contoh lengkap, mari kita bangun potongan script yang menghitung ukuran sebuah direktori, sekaligus mendukung opsi -d untuk menentukan direktori target (wajib disertai argumen) dan opsi -a sebagai flag biasa tanpa argumen.
#!/bin/bash
tampilkan_semua=0
direktori="."
while getopts "d:a" opt; do
case "$opt" in
d) direktori="$OPTARG" ;;
a) tampilkan_semua=1 ;;
esac
done
echo "Direktori target: $direktori"
echo "Tampilkan semua: $tampilkan_semua"Menjalankan ./ukuran.sh -d /var/log -a menampilkan Direktori target: /var/log dan Tampilkan semua: 1. Dokumentasi resmi Bash merekomendasikan argumen sebuah opsi dipisahkan dari huruf opsinya oleh whitespace seperti -d /var/log di atas, gaya penulisan yang paling umum dipakai dan paling gampang dibaca ulang. Meski begitu, sesuai spesifikasi POSIX Shell Command Language, getopts tetap menerima bentuk tertempel tanpa spasi seperti -d/var/log: begitu huruf opsi d dikenali butuh argumen, sisa karakter yang menempel langsung di argumen yang sama otomatis dibaca sebagai OPTARG, pola yang juga lazim dipakai tool lain seperti grep -A3 untuk menampilkan tiga baris konteks setelah baris yang cocok. Biasakan tetap memisahkan opsi dan nilainya dengan spasi di script produksi supaya konsisten dan gampang dipindai pembaca lain, meski secara teknis keduanya valid. Wrapping tool du yang muncul di contoh ini akan dibahas lebih dalam sebagai bagian dari administrasi sistem di Bab 17.
12.2.3 Mode Error: Verbose vs Silent
Karakter pertama pada optstring menentukan bagaimana getopts bersikap saat menemukan opsi tidak dikenal atau opsi yang argumennya tidak diberikan. Kalau optstring tidak diawali titik dua, getopts berjalan di mode verbose (perilaku default): ia langsung mencetak pesan error ke stderr dan mengisi variabel opt dengan ? untuk kedua jenis kesalahan itu, sehingga kita tidak bisa membedakan dari dalam script apakah kesalahannya berupa opsi tak dikenal atau argumen yang hilang. Menambahkan titik dua di paling depan optstring mengaktifkan mode silent, yang mematikan pesan otomatis itu dan justru membedakan dua jenis kesalahan tersebut lewat isi opt dan OPTARG, sehingga script bisa menyusun pesan error sendiri yang lebih jelas dan konsisten dengan gaya pesan lain di script.
| Situasi | Mode Verbose (default) | Mode Silent (optstring diawali :) |
|---|---|---|
| Opsi tidak dikenal | opt berisi ?, OPTARG di-unset, pesan error tercetak | opt berisi ?, OPTARG berisi karakter opsi tak dikenal itu, tanpa pesan |
| Argumen wajib tidak diberikan | opt berisi ?, OPTARG di-unset, pesan error tercetak | opt berisi :, OPTARG berisi karakter opsi yang kehilangan argumennya, tanpa pesan |
while getopts ":d:ah" opt; do
case "$opt" in
d) direktori="$OPTARG" ;;
a) tampilkan_semua=1 ;;
h) tampilkan_bantuan; exit 0 ;;
:) echo "Error: opsi -$OPTARG butuh sebuah argumen" >&2; exit 1 ;;
\?) echo "Error: opsi tidak dikenal: -$OPTARG" >&2; exit 1 ;;
esac
donePola di atas, dengan optstring diawali titik dua ditambah dua cabang case untuk : dan \?, adalah idiom baku yang sangat umum ditemui di script Bash siap produksi, karena mode silent memberi kita kendali penuh atas format pesan error, konsisten dengan pesan-pesan lain di script kita sendiri, alih-alih mengandalkan format pesan bawaan getopts yang tidak selalu cocok dengan gaya penulisan script kita.
12.2.4 Menyisakan Argumen lewat OPTIND
Setelah getopts selesai memproses seluruh opsi yang dikenali, ia berhenti begitu menemukan argumen yang bukan opsi, atau begitu menemukan pemisah -- yang secara eksplisit menandai akhir opsi. Pada titik itu, OPTIND sudah menunjuk ke indeks argumen pertama yang tersisa dan belum diproses. Karena positional parameter ($1, $2, dst.) tidak otomatis berubah hanya karena getopts selesai berjalan, kita perlu menggesernya secara manual memakai shift yang sudah dibahas di 12.1.3, supaya argumen yang tersisa itu kembali bisa diakses lewat $1.
#!/bin/bash
verbose=0
while getopts ":v" opt; do
case "$opt" in
v) verbose=1 ;;
\?) echo "Error: opsi tidak dikenal: -$OPTARG" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
echo "Verbose: $verbose"
echo "Argumen tersisa: $*"Menjalankan ./deploy.sh -v staging menampilkan Verbose: 1 dan Argumen tersisa: staging, karena shift $((OPTIND - 1)) membuang seluruh argumen yang sudah dikonsumsi getopts (dalam kasus ini cuma -v, satu argumen), menyisakan staging sebagai $1 yang baru. Pola shift $((OPTIND - 1)) ini nyaris selalu muncul tepat setelah loop getopts selesai di script produksi mana pun, karena kombinasi opsi di depan dan argumen biasa di belakang (misalnya nama environment, path file, atau nama service) adalah pola paling umum dipakai tool command-line di dunia nyata.
12.3 Menambahkan Flag: -v, --verbose, --help
getopts di 12.2 sangat andal untuk opsi satu huruf seperti -v, tapi tool command-line modern hampir selalu menawarkan padanan panjangnya juga, seperti --verbose atau --help, yang lebih mudah diingat dan dibaca ulang di dalam script atau dokumentasi. Sub-bab ini membahas kenapa getopts tidak bisa menangani opsi panjang semacam itu, dan cara membangun parser sendiri yang mendukung keduanya sekaligus.
12.3.1 Keterbatasan getopts untuk Long Option
getopts, sesuai spesifikasinya, hanya dirancang untuk mem-parsing opsi satu karakter diawali satu tanda hubung, seperti -v atau -f nama_file. Ia tidak mengenal sama sekali konsep long option, yaitu opsi berupa kata penuh diawali dua tanda hubung seperti --verbose atau --file nama_file. Memberikan --verbose ke script yang parsing opsinya masih memakai getopts polos bukan cuma diabaikan begitu saja, melainkan memicu kekacauan yang lebih membingungkan: getopts membaca tanda hubung kedua di --verbose sebagai huruf opsi tak dikenal, lalu tiap huruf sisanya (v, e, r, b, o, s, e) diproses satu per satu seolah masing-masing adalah opsi terpisah. Kalau kebetulan huruf v memang terdaftar valid di optstring, huruf itu di tengah kata --verbose ikut ke-trigger sebagai flag -v yang sama sekali tidak dimaksudkan pengguna, sementara huruf-huruf lain memicu serangkaian pesan opsi tidak dikenal. Perilaku inilah yang membuat opsi panjang sama sekali tidak cocok dipasangkan dengan getopts polos, bukan sekadar "diabaikan" seperti argumen biasa.
| Pendekatan | Short Option (-v) | Long Option (--verbose) | Catatan |
|---|---|---|---|
getopts (builtin Bash) | Didukung penuh | Tidak didukung | Portable, tersedia otomatis di setiap Bash tanpa dependensi tambahan |
Parsing manual (while + case) | Didukung, ditulis sendiri | Didukung, ditulis sendiri | Lebih banyak baris kode, tapi fleksibel dan tetap portable |
Perintah eksternal getopt (bukan getopts) | Didukung | Didukung lewat opsi --long | Bergantung implementasi sistem (GNU getopt vs versi util-linux), riwayat portabilitasnya lebih rumit |
Ada juga perintah eksternal terpisah bernama getopt (tanpa huruf s di akhir) yang bisa menangani opsi panjang lewat opsi --long, tapi perilakunya berbeda-beda tergantung sistem operasi dan sengaja tidak dibahas mendalam di sini karena portabilitasnya kurang konsisten dibanding builtin getopts. Pendekatan yang paling umum dipakai dan tetap portable di lapangan, dan yang akan kita tulis di 12.3.2, adalah parsing manual memakai kombinasi while dan case yang sudah dikenal sejak Bab 6.3 dan 7.2.
12.3.2 Parsing Manual dengan while dan case
Pola parsing manual pada dasarnya mirip loop while [[ $# -gt 0 ]] yang sudah dibahas di 12.1.3, hanya saja isi case-nya sekarang mencocokkan pola string opsi secara langsung, mendukung bentuk pendek maupun panjang sekaligus lewat operator | (atau) pada case.
#!/bin/bash
verbose=0
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose)
verbose=1
shift
;;
-h|--help)
echo "Penggunaan: $(basename "$0") [-v|--verbose] [-h|--help]"
exit 0
;;
-*)
echo "Error: opsi tidak dikenal: $1" >&2
exit 1
;;
*)
echo "Error: argumen tak terduga: $1" >&2
exit 1
;;
esac
done
echo "Verbose: $verbose"Cabang -v|--verbose) di atas menangkap kedua bentuk opsi sekaligus dalam satu blok, dan karena opsi ini tidak membutuhkan argumen tambahan, cukup shift satu kali untuk membuangnya dari daftar positional parameter. Dua cabang penutup, -*) dan *), berperan sebagai jaring pengaman: -*) menangkap apa pun yang diawali tanda hubung tapi tidak cocok dengan opsi mana pun di atasnya (opsi tidak dikenal), sementara *) menangkap argumen apa pun yang sama sekali tidak diawali tanda hubung padahal script ini belum mengharapkan argumen semacam itu. Urutan dua cabang ini penting: -*) wajib diletakkan sebelum *), karena case mencocokkan pola dari atas ke bawah dan berhenti di kecocokan pertama, seperti dibahas di Bab 6.3.2.
12.3.3 Menangani Format --opsi=nilai
Opsi panjang yang butuh nilai, seperti --file, lazimnya mendukung dua gaya penulisan sekaligus: dipisah spasi (--file akses.log) atau digabung dengan tanda sama dengan (--file=akses.log). Mendukung gaya kedua butuh satu bentuk parameter expansion baru yang belum dibahas di Bab 9.4, yaitu ${var#pola}, yang menghapus kemunculan pola paling pendek yang cocok di awal nilai var. Berbeda dari ${var/pola/pengganti} di Bab 9.4.3 yang mengganti teks, ${var#pola} murni memotong bagian depannya saja tanpa mengganti apa pun.
--file=akses.logUntuk memisahkan nilai akses.log dari opsi di atas, kita memanfaatkan ${1#*=}: pola *= di sini berarti "sembarang karakter diikuti tanda sama dengan", dan karena diterapkan dari awal string, hasilnya adalah seluruh teks setelah tanda = pertama.
#!/bin/bash
file=""
while [[ $# -gt 0 ]]; do
case "$1" in
-f|--file)
file="$2"
shift 2
;;
--file=*)
file="${1#*=}"
shift
;;
*)
echo "Error: argumen tak terduga: $1" >&2
exit 1
;;
esac
done
echo "File: $file"Perhatikan dua cabang --file berbeda di atas. Cabang -f|--file) menangani gaya dipisah spasi, mengambil nilainya dari $2 lalu membuang dua argumen sekaligus lewat shift 2 (huruf opsi dan nilainya). Cabang --file=*) menangani gaya digabung tanda sama dengan, mengekstrak nilainya lewat ${1#*=} yang sudah dijelaskan di atas, lalu cukup shift satu kali karena opsi dan nilainya sudah menyatu dalam satu argumen tunggal. Kedua gaya ini menghasilkan file="akses.log" yang sama persis, tinggal dipilih Developer atau Sysadmin yang memanggil script sesuai kebiasaan masing-masing.
12.3.4 Menyusun Pesan Bantuan --help
Opsi -h/--help adalah konvensi hampir universal di tool command-line Linux: begitu dipanggil, script menampilkan ringkasan cara pemakaiannya lalu keluar dengan exit status 0, tanpa menjalankan logika utama apa pun. Kebiasaan yang baik adalah menaruh teks bantuan ini dalam satu fungsi tersendiri, konsep fungsi yang sudah dibahas di Bab 8, supaya bisa dipanggil dari mana saja, termasuk saat validasi argumen gagal seperti dibahas di 12.1.4.
tampilkan_bantuan() {
cat <<EOF
Penggunaan: $(basename "$0") [-v|--verbose] [-h|--help] FILE
Opsi:
-v, --verbose Tampilkan detail proses
-h, --help Tampilkan bantuan ini lalu keluar
EOF
}Heredoc lewat <<EOF ... EOF di atas, konsep redirection yang disinggung sekilas di Bab 5.3, jadi cara paling rapi menuliskan teks multi-baris tanpa perlu echo berulang kali per baris. Menaruh pesan bantuan di fungsi terpisah seperti ini juga memudahkan konsistensi: pesan yang sama bisa dipanggil ulang saat opsi tidak dikenal ditemukan (lihat cabang -*) di 12.3.2), sehingga pengguna script langsung tahu opsi apa saja yang valid begitu salah ketik, bukan cuma mendapat pesan error tanpa petunjuk lanjutan.
12.4 Studi Kasus: CLI Tool Sederhana
Sub-bab penutup ini menggabungkan seluruh teknik dari 12.1 sampai 12.3 untuk membangun satu CLI tool utuh bernama cari-log.sh, pembungkus sederhana di atas grep yang sudah dibahas di Bab 9.2, dengan opsi pendek dan panjang sekaligus, validasi argumen wajib, dan pesan bantuan. Skenario ini mewakili kebutuhan yang sering dihadapi Sysadmin: alih-alih mengetik ulang perintah grep yang panjang setiap kali perlu menelusuri log, opsi-opsi yang sering dipakai dibungkus jadi satu tool ringkas yang gampang diingat.
12.4.1 Spesifikasi CLI Tool cari-log.sh
Sebelum menulis kode, kita rangkai dulu spesifikasinya, kebiasaan yang baik untuk script apa pun yang opsinya lebih dari dua atau tiga buah, supaya tidak ada opsi yang terlewat atau salah maknanya di tengah proses menulis.
| Opsi | Bentuk Panjang | Butuh Argumen | Keterangan |
|---|---|---|---|
-f | --file | Ya (wajib) | Path file log yang diperiksa |
-p | --pattern | Ya (wajib) | Pola pencarian, dalam bentuk regex ERE (Bab 9.3.3) |
-i | --ignore-case | Tidak | Pencarian case-insensitive, diteruskan sebagai -i ke grep |
-c | --count | Tidak | Tampilkan jumlah baris yang cocok, bukan isinya, setara grep -c |
-v | --verbose | Tidak | Tampilkan info proses ke stderr sebelum pencarian dijalankan |
-h | --help | Tidak | Tampilkan bantuan lalu keluar |
Satu catatan penting soal penamaan: opsi -v pada cari-log.sh ini sengaja berarti verbose, bukan invert match seperti -v pada grep asli yang sudah dibahas di Bab 9.2.3. Ini jebakan penamaan yang cukup umum ditemui di lapangan, karena huruf opsi yang tersedia terbatas, tapi maknanya bisa berbeda antar tool. Kalau sebuah wrapper script meneruskan opsi mentah ke tool lain di baliknya, dokumentasikan dengan jelas lewat --help setiap kali ada potensi makna ganda seperti ini, supaya pengguna script tidak salah asumsi menyamakan perilakunya dengan tool aslinya.
12.4.2 Implementasi Script Lengkap
Berikut implementasi lengkap cari-log.sh, menggabungkan parsing manual dari 12.3 (karena opsi panjang wajib didukung sesuai spesifikasi di 12.4.1), validasi argumen dari 12.1.4, dan array dari Bab 4 untuk menyusun opsi grep secara dinamis.
#!/bin/bash
tampilkan_bantuan() {
cat <<EOF
Penggunaan: $(basename "$0") -f FILE -p POLA [OPSI]
Mencari POLA (regex ERE) di dalam FILE dan menampilkan baris yang cocok.
Opsi wajib:
-f, --file FILE File log yang akan diperiksa
-p, --pattern POLA Pola pencarian (regex ERE)
Opsi tambahan:
-i, --ignore-case Abaikan perbedaan huruf besar/kecil
-c, --count Tampilkan jumlah baris yang cocok, bukan isinya
-v, --verbose Tampilkan info proses ke stderr
-h, --help Tampilkan bantuan ini lalu keluar
EOF
}
file=""
pola=""
ignore_case=0
hitung_saja=0
verbose=0
while [[ $# -gt 0 ]]; do
case "$1" in
-f|--file)
file="$2"
shift 2
;;
--file=*)
file="${1#*=}"
shift
;;
-p|--pattern)
pola="$2"
shift 2
;;
--pattern=*)
pola="${1#*=}"
shift
;;
-i|--ignore-case)
ignore_case=1
shift
;;
-c|--count)
hitung_saja=1
shift
;;
-v|--verbose)
verbose=1
shift
;;
-h|--help)
tampilkan_bantuan
exit 0
;;
--)
shift
break
;;
-*)
echo "Error: opsi tidak dikenal: $1" >&2
tampilkan_bantuan >&2
exit 1
;;
*)
echo "Error: argumen tak terduga: $1" >&2
exit 1
;;
esac
done
if [[ -z "$file" || -z "$pola" ]]; then
echo "Error: --file dan --pattern wajib diisi" >&2
exit 1
fi
if [[ ! -f "$file" ]]; then
echo "Error: file tidak ditemukan: $file" >&2
exit 1
fi
opsi_grep=(-E)
[[ "$ignore_case" -eq 1 ]] && opsi_grep+=(-i)
[[ "$hitung_saja" -eq 1 ]] && opsi_grep+=(-c)
if [[ "$verbose" -eq 1 ]]; then
echo "Mencari pola '$pola' di file '$file'..." >&2
fi
grep "${opsi_grep[@]}" "$pola" "$file"Array opsi_grep di sini menyusun daftar opsi grep secara dinamis berdasarkan flag apa saja yang diaktifkan pengguna, teknik menambah elemen array lewat +=() yang sudah dibahas di Bab 4.3. Cara ini jauh lebih aman dibanding menyusun opsi sebagai satu string panjang lalu memisahkannya lagi, karena tiap elemen array tetap terjaga sebagai satu kesatuan utuh saat nanti dikembangkan lewat "${opsi_grep[@]}", konsisten dengan alasan kenapa "$@" dikutip di 12.1.2. Cabang --) berfungsi sebagai penanda akhir opsi eksplisit, konvensi umum di tool Linux yang berguna kalau suatu saat argumen positional yang diterima script kebetulan diawali tanda hubung juga.
12.4.3 Menguji dan Memverifikasi Script
Setelah script ditulis, langkah wajib berikutnya adalah mengujinya dengan beberapa skenario berbeda, kebiasaan verifikasi yang konsisten dengan pola di bab-bab sebelumnya. Beri izin eksekusi lebih dulu sesuai Bab 1.4, lalu jalankan beberapa kombinasi opsi.
chmod +x cari-log.sh
./cari-log.sh --help
./cari-log.sh -f /var/log/aplikasi.log -p "error"
./cari-log.sh --file=/var/log/aplikasi.log --pattern="error" --ignore-case --count
./cari-log.sh -p "error"Dua perintah pertama seharusnya menampilkan teks bantuan dan baris-baris log yang mengandung error. Perintah ketiga membuktikan gaya --opsi=nilai dari 12.3.3 berjalan berdampingan dengan flag tanpa argumen seperti --ignore-case dan --count, menampilkan satu angka berupa jumlah baris yang cocok. Perintah keempat sengaja menghilangkan --file untuk memicu validasi di 12.4.2, dan seharusnya berhenti dengan pesan Error: --file dan --pattern wajib diisi beserta exit status 1, bisa diperiksa segera lewat echo $? seperti dibahas di Bab 6.4.1.
12.4.4 Troubleshooting: Argumen Mengandung Spasi
Jebakan yang paling sering ditemui Sysadmin maupun Developer saat memakai CLI tool semacam ini di lapangan adalah lupa mengutip argumen yang mengandung spasi, terutama untuk path file di direktori bernama panjang seperti /home/user/My Documents/aplikasi.log.
./cari-log.sh -f /home/user/My Documents/aplikasi.log -p "error"Perintah di atas gagal dengan pesan argumen tak terduga: Documents/aplikasi.log, bukan karena bug di script, melainkan karena shell sudah memecah /home/user/My Documents/aplikasi.log jadi dua argumen terpisah sebelum sempat sampai ke cari-log.sh, persis mekanisme word splitting yang sudah dibahas di Bab 3.4. Nilai /home/user/My tertangkap sebagai argumen untuk -f, sementara Documents/aplikasi.log tersisa sebagai argumen yang tidak dikenali cabang mana pun di case. Solusinya konsisten dengan pembahasan quoting sejak Bab 3.4: selalu bungkus argumen yang berpotensi mengandung spasi dengan tanda kutip ganda saat memanggil script.
./cari-log.sh -f "/home/user/My Documents/aplikasi.log" -p "error"Kebiasaan yang sama juga berlaku di sisi penulis script: setiap kali sebuah variabel yang menyimpan hasil parsing argumen dipakai lagi di dalam script, seperti "$file" dan "$pola" pada cari-log.sh di 12.4.2, variabel itu tetap wajib dikutip. Menghilangkan kutip di titik ini membuat masalah yang sama muncul kembali, meski argumen sudah berhasil ditangkap dengan benar di tahap parsing.
Menguasai penanganan argumen dari $1 mentah sampai CLI tool lengkap seperti cari-log.sh menutup Bagian 1 series ini. Bagian 2 dimulai di Bab 13 dengan arah yang lebih spesifik: menerapkan seluruh fondasi Bash yang sudah dibangun sejak Bab 1, termasuk penanganan argumen di bab ini, untuk mengotomatisasi tugas harian di desktop, mulai dari rename file massal sampai backup folder penting secara otomatis.

