Argumen dan flag

Penguraian argumen dan flag dengan getopts, pesan penggunaan, serta validasi masukan skrip.

Daftar isi

Skrip yang layak dipakai berulang tidak menaruh nama berkas dan pengaturannya di dalam kode, melainkan menerimanya sebagai argumen dan flag saat dipanggil. Topik ini membahas cara membaca parameter posisional, menguraikan flag dengan getopts, menyusun pesan penggunaan, serta memvalidasi masukan sebelum diproses. Seluruh contoh memakai satu skrip statistik berkas teks yang menerima flag opsional -a dan -t beserta satu nama berkas. Skrip yang dipanggil dengan cara yang salah akan menolak pemanggilan itu dengan pesan yang jelas dan kode keluar yang konsisten.

Parameter posisional dan keterbatasannya#

Bash menyerahkan argumen baris perintah kepada skrip melalui parameter posisional, yaitu $1 untuk argumen pertama, $2 untuk argumen kedua, dan seterusnya, sedangkan $# menyimpan jumlah argumennya. Cara ini tepat untuk masukan yang posisinya tetap, misalnya nama berkas sumber dan nama berkas tujuan. Masalah muncul ketika skrip menerima flag opsional yang boleh ada atau tidak dan urutannya bebas, apalagi bila sebuah flag membutuhkan nilai tersendiri seperti -t 2. Memeriksa $1 dan $2 satu per satu dengan percabangan akan cepat berantakan karena kombinasi kemungkinannya bertambah banyak. Kebutuhan semacam ini ditangani oleh penguraian (parsing), yaitu membaca argumen satu per satu sesuai aturan sintaks yang ditetapkan skripnya.

Selain parameter bernomor, bash juga menyediakan $@ dan $* yang mewakili seluruh argumen sekaligus, sedangkan $0 menyimpan nama skrip yang sedang berjalan. Untuk kebutuhan sederhana, membaca $1 langsung sudah cukup. Kombinasi $@ baru menjadi penting apabila skrip meneruskan seluruh argumennya kepada perintah lain, misalnya pada wrapper (skrip pembungkus yang memanggil perintah lain), dan kasus semacam itu berada di luar lingkup topik ini.

Kerangka skrip statistik berkas#

Skrip pada bagian ini menghitung statistik sebuah berkas teks, yaitu jumlah baris dan jumlah kata, dengan dua flag opsional. Flag -a menambahkan jumlah karakter ke keluaran, sedangkan flag -t N mencetak N baris pertama berkas. Kerangkanya terdiri atas tiga bagian dengan urutan yang tetap, yaitu fungsi pesan penggunaan, perulangan penguraian flag, dan validasi akhir.

bash skrip-hitung.sh
# tidak-dijalankan
# Statistik berkas teks: jumlah baris, jumlah kata, dan jumlah karakter.

set -euo pipefail

pesan_penggunaan() {
  echo "Penggunaan: skrip-hitung.sh [-a] [-t N] BERKAS"
  echo "  -a      sertakan jumlah karakter"
  echo "  -t N    tampilkan N baris pertama berkas"
}

salah_penggunaan() {
  echo "salah: $1" >&2
  pesan_penggunaan >&2
  exit 2
}

tampilkan_karakter=false
batas_baris=0

while getopts ":at:" opsi; do
  case "$opsi" in
    a)
      tampilkan_karakter=true
      ;;
    t)
      batas_baris="$OPTARG"
      ;;
    :)
      salah_penggunaan "opsi -$OPTARG membutuhkan nilai"
      ;;
    \?)
      salah_penggunaan "opsi -$OPTARG tidak dikenal"
      ;;
  esac
done
shift $((OPTIND - 1))

if [ "$#" -ne 1 ]; then
  salah_penggunaan "diperlukan tepat satu nama berkas"
fi

berkas="$1"

if [ ! -f "$berkas" ]; then
  salah_penggunaan "berkas '$berkas' tidak ditemukan"
fi

echo "baris: $(wc -l < "$berkas")"
echo "kata: $(wc -w < "$berkas")"
if [ "$tampilkan_karakter" = true ]; then
  echo "karakter: $(wc -c < "$berkas")"
fi
if [ "$batas_baris" -gt 0 ]; then
  head -n "$batas_baris" "$berkas"
fi

Baris pertama skrip ini bertanda # tidak-dijalankan karena mengaktifkan strict mode, sehingga skrip selalu dijalankan sebagai skrip anak melalui bash skrip-hitung.sh. Tiga hal perlu diperhatikan pada isinya. Fungsi salah_penggunaan mengirim pesan ke aliran error melalui >&2 dan mengakhiri skrip dengan kode keluar 2, sehingga kegagalan penggunaan mudah dibedakan dari kegagalan perintah. Perulangan while getopts ":at:" opsi menguraikan flag satu per satu, sedangkan shift $((OPTIND - 1)) menggeser flag keluar dari daftar argumen sehingga $1 menunjuk nama berkasnya. Validasi [ ! -f "$berkas" ] memastikan berkas benar-benar ada sebelum statistik dihitung.

Menjalankan skrip dengan flag#

Siapkan berkas contoh berisi tiga baris catatan stok, lalu jalankan skrip tanpa flag tambahan. Pemanggilan ini hanya memakai satu argumen posisional, sehingga bagian penguraian flag selesai tanpa membaca apa pun.

bash
# siapkan berkas contoh stok.txt
printf 'Produk A: 3 unit\nProduk B: 5 unit\nProduk C: 2 unit\n' > stok.txt
# jalankan tanpa flag tambahan
bash skrip-hitung.sh stok.txt
text Keluaran
baris: 3
kata: 12

Tanpa flag, keluaran hanya memuat dua statistik dasar, yaitu tiga baris dan dua belas kata. Aktifkan kedua flag sekaligus untuk melihat pengaruhnya.

bash
# sertakan jumlah karakter dan dua baris pertama
bash skrip-hitung.sh -a -t 2 stok.txt
text Keluaran
baris: 3
kata: 12
karakter: 51
Produk A: 3 unit
Produk B: 5 unit

Flag -a menambahkan baris karakter: 51, sedangkan flag -t 2 mencetak dua baris pertama berkas sumber. Statistik baris dan kata tidak berubah antara kedua pemanggilan, karena flag hanya menambahkan keluaran, bukan mengubah cara berkas dihitung. Keluaran tambahan muncul sesuai urutan pemeriksaan pada skrip, yaitu jumlah karakter lebih dahulu, kemudian cuplikan barisnya. Urutan antarflag pada baris perintah sendiri bebas karena getopts menguraikannya satu per satu sampai menemui argumen pertama yang bukan flag. Nilai -t juga tidak terbatas pada angka kecil, misalnya -t 100 akan mencetak sampai seratus baris atau sampai berkas habis, mana yang lebih dulu tercapai.

Validasi masukan dan pesan penggunaan#

Skrip yang baik menolak masukan yang salah sebelum memprosesnya, dan penolakannya menyebut apa yang salah serta cara pemanggilan yang benar. Fungsi pesan_penggunaan menyimpan teks cara pemakaian, sedangkan salah_penggunaan mencetak penyebab kegagalan lalu pesan penggunaan ke aliran error. Empat pemanggilan berikut memicu satu jalur validasi masing-masing.

bash
# panggil tanpa argumen sama sekali
bash skrip-hitung.sh
echo "kode keluar: $?"
text Keluaran
salah: diperlukan tepat satu nama berkas
Penggunaan: skrip-hitung.sh [-a] [-t N] BERKAS
  -a      sertakan jumlah karakter
  -t N    tampilkan N baris pertama berkas
kode keluar: 2
bash
# gunakan flag yang tidak dikenal
bash skrip-hitung.sh -x stok.txt
echo "kode keluar: $?"
text Keluaran
salah: opsi -x tidak dikenal
Penggunaan: skrip-hitung.sh [-a] [-t N] BERKAS
  -a      sertakan jumlah karakter
  -t N    tampilkan N baris pertama berkas
kode keluar: 2
bash
# beri flag -t tanpa nilai
bash skrip-hitung.sh -t
echo "kode keluar: $?"
text Keluaran
salah: opsi -t membutuhkan nilai
Penggunaan: skrip-hitung.sh [-a] [-t N] BERKAS
  -a      sertakan jumlah karakter
  -t N    tampilkan N baris pertama berkas
kode keluar: 2
bash
# tunjuk berkas yang tidak ada
bash skrip-hitung.sh lama.txt
echo "kode keluar: $?"
text Keluaran
salah: berkas 'lama.txt' tidak ditemukan
Penggunaan: skrip-hitung.sh [-a] [-t N] BERKAS
  -a      sertakan jumlah karakter
  -t N    tampilkan N baris pertama berkas
kode keluar: 2

Keempat kasus menghasilkan pola yang sama, yaitu satu baris penyebab yang diawali salah:, diikuti pesan penggunaan, lalu kode keluar 2. Angka 2 dipilih secara konsisten sebagai penanda kegagalan penggunaan, sehingga tidak tertukar dengan kode keluar 1 yang dihasilkan kegagalan perintah di dalam skrip. Konsistensi kode keluar membuat skrip lain dapat menilai kegagalan tanpa membaca teksnya, sedangkan teks pesan mengarahkan pengguna ke pemanggilan yang benar. Perhatikan pula bahwa seluruh pesan penolakan dicetak ke aliran error, sehingga keluaran normal tetap bersih dari teks bantuan apabila pemanggil mengalihkan keduanya ke tujuan yang berbeda. Karena seluruh perluasan variabel dikutip dan setiap jalur kegagalan ditangani, analisis statis shellcheck tidak menemukan temuan pada skrip ini.

bash
# pastikan skrip bebas dari temuan analisis statis
shellcheck --shell=bash skrip-hitung.sh && echo 'meta: hitung bersih'
text Keluaran
meta: hitung bersih

Cara kerja getopts#

Perintah getopts adalah builtin (perintah yang disediakan langsung oleh shell, bukan berkas program terpisah) yang menangani satu flag pada setiap pemanggilannya, sehingga biasanya ditempatkan di dalam perulangan while. Argumen pertamanya berupa optstring, yaitu deretan huruf flag yang dianggap sah. Titik dua setelah huruf berarti flag tersebut membutuhkan nilai, sehingga ":at:" mendefinisikan flag -a tanpa nilai dan flag -t dengan nilai. Titik dua di awal optstring mengaktifkan mode senyap, yaitu mode yang menyerahkan pelaporan kesalahan kepada skrip melalui cabang : dan \? pada case, alih-alih mencetak pesan bawaan getopts.

Dalam setiap putaran, variabel opsi berisi huruf flag yang baru dibaca, sedangkan $OPTARG menyimpan nilainya bila flag membutuhkan satu. Variabel $OPTIND menghitung posisi argumen berikutnya, sehingga setelah perulangan berakhir, shift $((OPTIND - 1)) membuang seluruh flag dari daftar parameter. Tanpa penggeseran itu, $1 masih berisi flag pertama dan bukan nama berkas. Perlu dicatat bahwa getopts hanya memproses flag pendek satu huruf, sehingga opsi panjang seperti --warnai harus diurai secara manual apabila memang diperlukan.

Kesalahan umum#

Kesalahan berikut sering muncul ketika penguraian flag baru ditulis pertama kali.

  • Melupakan shift $((OPTIND - 1)). Tanpa penggeseran itu, jumlah argumen masih menghitung flag, sehingga validasi menolak pemanggilan yang sebenarnya benar. Tambahkan penggeseran tepat setelah perulangan while berakhir.
  • Menulis optstring tanpa titik dua di awal. Tanpa itu, getopts mencetak pesan kesalahannya sendiri ke aliran error, sehingga pesan penggunaan milik skrip tidak muncul dan format keluaran tidak terkendali. Awali optstring dengan : agar setiap kesalahan ditangani cabang : dan \?.
  • Membaca berkas dengan [ -f $berkas ] tanpa tanda kutip. Nama berkas yang mengandung spasi terpecah menjadi beberapa kata, sehingga pemeriksaan menilai berkas yang keliru. Beri tanda kutip ganda pada $berkas, sebagaimana [ ! -f "$berkas" ] pada skrip contoh.

Ringkasan#

  • Parameter posisional cocok untuk argumen dengan posisi tetap, sedangkan flag opsional diurai dengan getopts di dalam perulangan while.
  • Optstring ":at:" menandai flag yang membutuhkan nilai, dan titik dua awalnya menyerahkan pelaporan kesalahan kepada skrip.
  • shift $((OPTIND - 1)) membuang flag dari daftar parameter sehingga $1 menunjuk argumen sesungguhnya.
  • Setiap masukan yang salah ditolak dengan pesan penggunaan dan kode keluar 2 yang konsisten.

Referensi#