Pada artikel kali ini, saya akan membahas mengenai Argument Parsing. Yakni adalah bagaimana cara agar setiap argumen yang diberikan pada suatu fungsi/script, bisa diinterpretasi sesuai posisinya (positional argument), atau sesuai ketentuan yang ditentukan nantinya.
Ini akan sangat bermanfaat jika Anda hendak membuat CLI tool dari bash script. Misalnya membuat tool seperti berikut:
|
|
Telah dibahas pada artikel yang telah lalu (Belajar Bash Scripting: Arguments) bahwa bash akan menginterpretasi setiap argumen yang diberikan dengan variabel $1
, $2
dan seterusnya.
Positional Argument
Yang dimaksud dengan positional argument, secara umum adalah argumen yang ditentukan dari urutannya ($1 .. $n
), dan secara khusus, yaitu argumen yang sudah ditetapkan tujuannya sesuai dengan urutan penyebutannya. Misalnya pada tool rsync
:
|
|
Bisa didapati bahwa argumen pertama merupakan spesifikasi untuk file sumber yang hendak ditransfer kemudian argumen terakhir adalah tujuannya, entah di lokal atau remote.
Sehingga, argumen pertama akan selalu menjadi sumber file dan argumen kedua akan selalu menjadi tujuannya. Jikalau dibalik, tidak bisa, destinasi tujuan didefinisikan pada argumen pertama dan seterusnya.
Atau pada utilitas mv
atau cp
, yang ketentuannya adalah jika diberikan 2 nama file, maka yang pertama menjadi sumber file dan yang kedua menjadi tujuan file (hasil salinan, atau hasil pindahan). Namun, jika diberikan lebih dari 2 file, maka argumen paling akhir akan menjadi tujuannya.
|
|
Maka sudah menjadi ketentuan, bahwa file/folder yang disebutkan paling akhir, maka file/folder tersebut akan menjadi targetnya.
Inilah yang dimaksud dengan positional argument.
Optional Argument
Optional argument adalah argument yang tidak wajib untuk diberikan. Boleh diberikan dan boleh juga tidak. Ini berfungsi untuk mengubah sifat suatu tool berdasarkan flag/option yang diberikan. Misalkan pada rysnc
:
|
|
Lho, katanya argumen pertama itu untuk spesifikasi file/folder sumber dan argumen kedua untuk tujuannya? Bukankah pada contoh ini, argumen pertama ($1
) adalah --dry-run
? kemudian argumen kedua adalah --delete
? dan yang ketiga dan keempat baru sumber dan tujuan?
Jawabannya, benar. Jika diruntut, memang terbaca seperti itu ($1 = '--dry-run'
dan seterusnya). Ini karena argument parsing. Tiap argumen yang diberikan akan diproses satu-persatu menyesuaikan dengan pola argumen yang diberikan.
Tool rsync
tersebut, diprogram sedemikian rupa sehingga ketika didapati ada argument yang berawalan --
atau -
maka akan dianggap sebagai optional parameter, dan akan diproses berdasarkan string setelahnya. Misalnya pada contoh diatas, --dry-run
maka rysnc
akan berjalan pada mode dry run alias percobaan.
Argument Parsing
Argument parsing sebenarnya bisa dilakukan dengan built-in tool getopts
. Namun, pada artikel ini saya hanya akan membahas argument parsing menggunakan while
dan for
loop yang dikombinasikan dengan kondisional case
.
Contoh skenarionya adalah seperti ini:
|
|
Maka akan didapatkan variabel $@
yang isinya:
|
|
dan $#
yang bernilai 7.
Variabel $#
bernilai sejumlah dengan seluruh argumen yang diberikan.
Dengan demikian maka bisa diproses masing-masing argumennya dengan loop berikut:
|
|
Penjelasan
Bagian while [[ $# -ne 0 ]]; do
akan melakukan loop selama $#
tidak bernilai nol, alias selama masih ada argumen yang bisa diproses, pemrosesan akan terus berlanjut.
Kemudian case "$1" in
akan mengecek argumen pertama dan akan mecocokkannya dengan case-case yang didefinisikan setelahnya. Kenapa hanya argumen pertama saja? Akan datang penjelasannya nanti.
Case-case yang dimaksud adalah seperti pada contoh, adalah -a
, -b
, --xyz
dan *
. Mari dibahas satu-persatu:
|
|
Nah, ketika $1
bernilai -a
, maka kita akan definisikan variabel option_a
dengan nilai set
. Kemudian ada sintaks shift
, yang berfungsi untuk menggser positional parameter yang diberikan. Maksudnya bagaimana?
Seperti yang sudah diketahui sebelumnya, bahwa pada contoh command sebelumnya, argumen-argumen yang diberikan adalah sebagai berikut:
|
|
Jika command shift
dipanggil, maka penyematan argumen akan menjadi seperti ini:
|
|
Kemudian, jika shift
dipanggil dan diberikan nilai padanya, maka pergeseran argumen akan dilakukan sebanyak nilai yang diberikan. Misalnya shift 2
:
|
|
Karena positional argument-nya bergeser, maka jumlahnya ($#
) juga berubah pula, sebagaimana pada contoh diatas.
Kembali ke pembahasan sebelumnya, karena kondisi $1
saat ini bernilai -a
maka kita isikan variabel option_a
dengan nilai set
dan kita geser positional argumen selanjutnya menggunakan perintah shift
. Sehingga kondisi $1
saat ini menjadi argumen setelahnya, tidak lagi -a
.
Setelah shift
diberlakukan, maka pengkondisian case
selesai dan kembali kepada loop untuk melakukan iterasi berikutnya karena $#
masih belum bernilai 0. Selanjutnya:
|
|
dengan kondisi $@
yang saat ini adalah seperti berikut ini:
|
|
maka, karena $1
saat ini bernilai -b
maka kita deklarasikan variabel option_b
dengan memberikannya nilai sesuai dengan nilai variabel $2
yaitu opt1
. Setelah itu kita geser kembali argumennya 2 kali. Sehingga, kondisi $@
saat ini menjadi:
|
|
Untuk bagian dibawah ini, pembahasannya sama dengan -b
:
|
|
Kemudian yang terkahir adalah:
|
|
Ini berarti, semua argumen yang bukan berupa option (diawali dengan -
), maka akan teranggap sebagai positional argument dan akan dimasukkan ke array positional_arg
yang akan diproses tersendiri nantinya.
Dengan demikian, output dari command berikut ini:
|
|
adalah
|
|
Parsing bentuk –opt=value dan -oValue
Setelah dijelaskan konsep dasar pemanfaatan WHILE loop dan CASE statement untuk argument parsing seperti pada contoh sebelumnya, saya akan jelaskan pula bagaimana cara untuk membuat parsing dengan model seperti ini:
|
|
Susunan WHILE loop sama seperti dengan contoh sebelumnya, namun bisa diperingkas seperti berikut agar mengurangi kedalaman indentasi:
|
|
Sintaks shift
pada contoh ini tidak lagi diletakkan pada tiap-tiap case yang diberikan karena itu redundan. Sehingga cukup diletakkan sekali saja setelah block case diakhir tiap loop.
Nah, pada contoh diatas, saya memanfaatkan suatu fitur dari Bash yaitu parameter expansion yang akan dibahas lebih detail pada artikel lain nantinya.
Parameter expansion yang dimanfaatkan adalah ${variabel#*PATTERN}
yang fungsinya adalah menghapus seluruh karakter dari awal hingga bertemu PATTERN. Pada contoh diatas adalah ${1#*=}
.
Anggap saja nilai $1
saat ini adalah --flag1=hehe
, fokus pada parameter #*=
, maka bisa diartikan dengan “menghapus seluruh karakter dari awal hingga bertemu dengan karakter =
(inklusif)”. Sehingga hasilnya adalah hehe
.
Kemudian, contoh selanjutnya adalah argument parsing semisal pada command mysql
:
|
|
Hampir sama dengan contoh sebelumnya, hanya berbeda pada sintaks parameter expansion saja dan pada klausa CASEnya:
|
|
Argument parsing dengan FOR loop
FOR loop juga bisa digunakan untuk melakukan argument parsing. Namun dengan batasan, bahwa argument parsing yang dilakukan tidak bisa berupa opsi/flag yang meminta value. Hanya bisa berupa opsi/flag yang bersifat toggle (alias, on/off) dan juga positional argument saja. Misal:
|
|
|
|
Pada model ini, kita memanfaatkan variabel $@
dan tidak perlu memanggil shift
karena tiap iterasinya, FOR loop akan mengkonsumsi semua ekspansi $@
satu persatu. Bentuknya menjadi lebih ringkas namun kurang fleksibel.
Validasi
Tentunya, setiap pemberian opsi dan/atau positional argument perlu diberlakukan validasi terlebih dahulu. Misalnya script tidak memperbolehkan penggunaan flag/opsi yang sama lebih dari satu, atau jika opsi yang satu sudah diberikan maka opsi yang lain tidak boleh diberikan pula.
Strategi untuk mengatasi kondisi semacam itu beragam. Diantaranya bisa dengan misalnya opsi yang terakhir diberikan maka itu yang akan dipertimbangkan. Atau pemberian prioritas, atau bahkan script akan error jika bertemu kondisi-kondisi tersebut.
Hal ini bisa dilakukan langsung pada bagian kondisional CASE atau setelah argument parsing selesai.
Contohnya pada utilitas mv
atau rm
pada opsi -i
dan -f
-nya:
|
|
Opsi -i
berfungsi agar perintah rm
memberikan prompt sebelum penghapusan file dilakukan. Sedangkan -f
adalah opsi yang digunakan agar rm
menghapus “paksa” suatu file tanpa menanyakan/memberitahukan terkait ada/tidaknya file yang dimaksud. Tentu keduanya bertentangan satu sama lain. Bagaimana rm
menangani kondisi ini? Yaitu dengan memprioritaskan opsi yang paling akhir.
Sehingga bisa saja suatu script disusun seperti ini untuk meniru gaya argument parsing pada cp
:
|
|
dengan demikian, variabel option
akan memiliki nilai sesuai dengan opsi yang ditentukan paling akhir.
Kemudian, suatu script dimana script tersebut memerlukan variabel tertentu agar diset oleh user, namun jika tidak diberikan, variabel tersebut akan terisi dengan nilai default. Misalnya pada tool iptables
. Tool tersebut memerlukan ditentukannya nama tabel agar bisa menampilkan semua rules-nya dengan flag -L
.
|
|
Jika, opsti -t nat
tidak diberikan, maka secara asal, iptables akan menampilkan rules pada tabel filter. Contoh pada script:
|
|
atau lebih sederhana, menggunakan parameter expansion:
|
|
Yang maknanya, jika variabel $2
itu UNSET
atau bernilai kosong (empty string), maka isi variabel table
dengan string filter
.
Bagaimana dengan bentuk option/flag seperti contoh dibawah ini?
|
|
Saya serahkan pada pembaca untuk mencari tahu.
Semoga bermanfaat, barakallahufiikum.