getopt, getopts atau parsing manual - apa yang harus digunakan ketika saya ingin mendukung opsi pendek dan panjang?


32

Saat ini saya sedang menulis skrip Bash yang memiliki persyaratan sebagai berikut:

  • itu harus dijalankan pada berbagai platform Unix / Linux
  • itu harus mendukung opsi pendek dan pendek (GNU)

Saya tahu itu getoptsakan menjadi cara yang disukai dalam hal portabilitas tetapi AFAIK tidak mendukung opsi lama.

getoptmendukung opsi panjang tetapi BashGuide sangat merekomendasikannya:

Jangan pernah gunakan getopt (1). getopt tidak dapat menangani string argumen kosong, atau argumen dengan spasi kosong. Tolong lupakan bahwa itu pernah ada.

Jadi, masih ada opsi penguraian manual. Ini rawan kesalahan, menghasilkan beberapa kode boilerplate, dan saya perlu menangani kesalahan sendiri (saya kira getopt(s)melakukan penanganan kesalahan sendiri).

Jadi, apa pilihan yang lebih disukai dalam kasus ini?


Jawaban:


9

Jika harus portabel untuk berbagai Unices, Anda harus tetap berpegang pada POSIX sh. Dan AFAIU di sana Anda tidak punya pilihan selain menggulirkan penanganan argumen dengan tangan.


25

getoptvs getoptstampaknya menjadi masalah agama. Adapun argumen terhadap getoptdi FAQ Bash :

  • " getopttidak dapat menangani string argumen kosong" tampaknya merujuk pada masalah yang diketahui dengan argumen opsional , yang sepertinya getoptstidak mendukung sama sekali (setidaknya dari membaca help getoptsuntuk Bash 4.2.24). Dari man getopt:

    getopt (3) dapat mengurai opsi panjang dengan argumen opsional yang diberikan argumen opsional kosong (tetapi tidak dapat melakukan ini untuk opsi pendek). Getopt (1) ini memperlakukan argumen opsional yang kosong seolah-olah tidak ada.

Saya tidak tahu dari mana getoptargumen "... tidak dapat menangani [...] dengan ruang kosong tertanam" berasal, tapi mari kita coba ini:

  • test.sh:

    #!/usr/bin/env bash
    set -o errexit -o noclobber -o nounset -o pipefail
    params="$(getopt -o ab:c -l alpha,bravo:,charlie --name "$0" -- "$@")"
    eval set -- "$params"
    
    while true
    do
        case "$1" in
            -a|--alpha)
                echo alpha
                shift
                ;;
            -b|--bravo)
                echo "bravo=$2"
                shift 2
                ;;
            -c|--charlie)
                echo charlie
                shift
                ;;
            --)
                shift
                break
                ;;
            *)
                echo "Not implemented: $1" >&2
                exit 1
                ;;
        esac
    done
    
  • menjalankan:

    $ ./test.sh -
    $ ./test.sh -acb '   whitespace   FTW   '
    alpha
    charlie
    bravo=   whitespace   FTW   
    $ ./test.sh -ab '' -c
    alpha
    bravo=
    charlie
    $ ./test.sh --alpha --bravo '   whitespace   FTW   ' --charlie
    alpha
    bravo=   whitespace   FTW   
    charlie
    

Sepertinya memeriksa dan kawin dengan saya, tapi saya yakin seseorang akan menunjukkan bagaimana saya salah memahami kalimat itu. Tentu saja masalah portabilitas masih ada; Anda harus memutuskan berapa banyak waktu yang berharga untuk berinvestasi dalam platform dengan Bash yang lebih tua atau tidak tersedia. Kiat saya sendiri adalah dengan menggunakan pedoman YAGNI dan KISS - Hanya dikembangkan untuk platform spesifik yang Anda tahu akan digunakan. Portabilitas kode shell umumnya mencapai 100% seiring waktu pengembangan hingga tak terbatas.


11
OP menyebutkan perlunya portabel untuk banyak platform Unix sementara getoptAnda mengutip di sini adalah spesifik Linux. Catatan yang getoptbukan bagian dari bash, itu bahkan bukan utilitas GNU dan di Linux dikirimkan dengan paket util-linux.
Stéphane Chazelas

2
Sebagian besar platform memiliki getopt, hanya Linux AFAIK yang hadir dengan yang mendukung opsi panjang atau kosong dalam argumen. Yang lain hanya akan mendukung sintaks Sistem V.
Stéphane Chazelas

7
getoptadalah perintah tradisional yang berasal dari Sistem V jauh sebelum Linux dirilis. getopttidak pernah distandarisasi. Tidak ada POSIX, Unix, atau Linux (LSB) yang pernah menstandardisasi getoptperintah. getoptsditentukan dalam ketiga tetapi tanpa dukungan untuk opsi panjang.
Stéphane Chazelas

1
Hanya untuk menambah diskusi ini: ini bukan tradisional getopt. Ini adalah rasa linux-utils seperti yang ditunjukkan oleh @ StéphaneChazelas. Ini memiliki opsi lawas yang akan menonaktifkan sintaks yang dijelaskan di atas, khususnya halaman manual menyatakan "GETOPT_COMPATIBLE pasukan bisa menggunakan format panggilan pertama seperti yang ditentukan dalam SYNOPSIS". Namun jika Anda dapat mengharapkan sistem target untuk menginstal paket ini, ini benar-benar cara yang tepat, karena getopt asli buruk dan getopt Bash sangat terbatas
n.caillou

1
Tautan OP yang mengklaim "Versi tradisional getopt tidak dapat menangani string argumen kosong, atau argumen dengan spasi kosong." dan bahwa getopt versi util-linux tidak boleh digunakan tidak memiliki bukti dan tidak lagi akurat. Survei cepat (5+ tahun kemudian) menunjukkan bahwa versi default getopt tersedia dari ArchLinux, SUSE, Ubuntu, RedHat, Fedora, dan CentOS (serta sebagian besar varian turunan) semuanya mendukung argumen dan argumen opsional dengan spasi putih.
mtalexan

10

Ada getopts_long ini ditulis sebagai fungsi shell POSIX yang dapat Anda sematkan di dalam skrip Anda.

Perhatikan bahwa Linux getopt(dari util-linux) berfungsi dengan benar ketika tidak dalam mode tradisional dan mendukung opsi-opsi panjang, tetapi mungkin bukan opsi untuk Anda jika Anda perlu portabel ke Unix lain.

Versi terbaru dari ksh93 ( getopts) dan zsh ( zparseopts) memiliki dukungan bawaan untuk mem -parsing opsi-opsi panjang yang mungkin menjadi pilihan bagi Anda karena tersedia untuk sebagian besar Unices (meskipun sering tidak diinstal secara default).

Opsi lain adalah menggunakan perldan Getopt::Longmodulnya yang keduanya harus tersedia di sebagian besar Unices saat ini, baik dengan menulis seluruh skrip perlatau hanya memanggil perl hanya untuk menguraikan opsi dan memberi makan informasi yang diekstrak ke shell. Sesuatu seperti:

parsed_ops=$(
  perl -MGetopt::Long -le '

    @options = (
      "foo=s", "bar", "neg!"
    );

    Getopt::Long::Configure "bundling";
    $q="'\''";
    GetOptions(@options) or exit 1;
    for (map /(\w+)/, @options) {
      eval "\$o=\$opt_$_";
      $o =~ s/$q/$q\\$q$q/g;
      print "opt_$_=$q$o$q"
    }' -- "$@"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...

Lihat perldoc Getopt::Longapa yang dapat dilakukan dan bagaimana perbedaannya dari parser opsi lainnya.


2

Setiap diskusi tentang masalah ini menyoroti opsi untuk menulis kode parsing secara manual - hanya dengan begitu Anda dapat yakin tentang fungsionalitas dan portabilitas. Saya menyarankan Anda untuk tidak menulis kode yang Anda dapat hasilkan dan hasilkan kembali oleh generator kode sumber terbuka yang mudah digunakan. Gunakan Argbash , yang telah dirancang untuk memberikan jawaban pasti untuk masalah Anda. Ini adalah generator kode yang terdokumentasi dengan baik tersedia sebagai aplikasi baris perintah , online atau sebagai gambar Docker .

Saya menyarankan agar perpustakaan bash, beberapa dari mereka menggunakan getopt(yang membuat sangat tidak dapat diakses) dan itu adalah rasa sakit untuk bundel gumpalan shell raksasa yang tidak dapat dibaca dengan skrip Anda.


0

Anda bisa menggunakan getoptpada sistem yang mendukungnya dan menggunakan kembali untuk sistem yang tidak.

Misalnya pure-getoptdiimplementasikan dalam Bash murni untuk menjadi pengganti drop-in untuk GNU getopt.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.