Bagaimana cara mengurai argumen opsional dalam skrip bash jika tidak ada pesanan yang diberikan?

13

Saya bingung bagaimana memasukkan argumen / bendera opsional saat menulis skrip bash untuk program berikut:

Program ini membutuhkan dua argumen:

run_program --flag1 <value> --flag2 <value>

Namun, ada beberapa flag opsional:

run_program --flag1 <value> --flag2 <value> --optflag1 <value> --optflag2 <value> --optflag3 <value> --optflag4 <value> --optflag5 <value> 

Saya ingin menjalankan skrip bash sehingga dibutuhkan argumen pengguna. Jika pengguna hanya memasukkan dua argumen secara berurutan, maka itu akan menjadi:

#!/bin/sh

run_program --flag1 $1 --flag2 $2

Tetapi bagaimana jika ada argumen opsional yang disertakan? Saya akan berpikir itu akan terjadi

if [ --optflag1 "$3" ]; then
    run_program --flag1 $1 --flag2 $2 --optflag1 $3
fi

Tetapi bagaimana jika $ 4 diberikan tetapi bukan $ 3?

ShanZhengYang
sumber
getoptsadalah apa yang kamu inginkan. Tanpa itu, Anda bisa menggunakan loop dengan pernyataan switch untuk mendeteksi setiap flag, opsional atau tidak.
orion
@ orion With getopts, saya perlu menentukan setiap kombinasi argumen? 3 & 4, 3 & 5, 3 & 4 & 5, dll?
ShanZhengYang
Tidak, Anda hanya mengaturnya jika Anda mendapatkannya, jika tidak melaporkannya tidak ditemukan, jadi pada dasarnya Anda hanya "mendapatkan" setiap opsi, dalam urutan apa pun, dan menentukannya dalam urutan apa pun, jika sama sekali. Tapi baca saja halaman bash man, semuanya ada di sana.
orion
@orion, saya minta maaf, tapi saya masih tidak mengerti getopts. Katakanlah saya memaksa pengguna untuk menjalankan skrip dengan semua argumen: run_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSEyang menjalankan program sebagai program --flag1 VAL --flag2 VAL. Jika Anda berlari run_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE, program akan berjalan sebagai program --flag1 VAL --flag2 VAL --optflag2 10. Bagaimana Anda bisa mendapatkan perilaku seperti itu getopts?
ShanZhengYang

Jawaban:

25

Artikel ini menunjukkan dua cara berbeda - shiftdan getopts(dan membahas kelebihan dan kekurangan dari kedua pendekatan).

Dengan shiftmelihat skrip Anda $1, tentukan tindakan apa yang harus diambil, lalu jalankan shift, pindah $2ke $1, $3ke $2, dll.

Sebagai contoh:

while :; do
    case $1 in
        -a|--flag1) flag1="SET"            
        ;;
        -b|--flag2) flag2="SET"            
        ;;
        -c|--optflag1) optflag1="SET"            
        ;;
        -d|--optflag2) optflag2="SET"            
        ;;
        -e|--optflag3) optflag3="SET"            
        ;;
        *) break
    esac
    shift
done

Dengan getoptsAnda menentukan opsi (pendek) dalam whileekspresi:

while getopts abcde opt; do
    case $opt in
        a) flag1="SET"
        ;;
        b) flag2="SET"
        ;;
        c) optflag1="SET"
        ;;
        d) optflag2="SET"
        ;;
        e) optflag3="SET"
        ;;
    esac
done

Jelas, ini hanya potongan kode, dan saya telah meninggalkan validasi - memeriksa bahwa args wajib flag1 dan flag2 diatur, dll.

Pendekatan yang Anda gunakan sampai batas tertentu adalah masalah selera - seberapa portabel Anda ingin skrip Anda, apakah Anda dapat hidup dengan opsi pendek (POSIX) saja atau apakah Anda ingin opsi panjang (GNU), dll.

John N.
sumber
Saya biasanya melakukan while (( $# ))alih - alih while :;dan sering berhenti dengan kesalahan dalam *kasus ini
Jasen
ini menjawab judul tetapi bukan pertanyaannya.
Jasen
@ Yasen, saya melihat ini tadi malam dan tidak bisa melihat kehidupan saya mengapa. Pagi ini jauh lebih jelas (dan saya sudah melihat jawaban orion sekarang juga). Saya akan menghapus jawaban ini hari ini (saya ingin mengakui komentar Anda terlebih dahulu, dan ini sepertinya cara termudah untuk melakukannya).
John N
tidak masalah, itu masih jawaban yang bagus, hanya pertanyaan yang mengelak.
Jasen
1
NB: Dalam hal set -o nounsetsolusi pertama akan kesalahan jika tidak ada parameter yang diberikan. Perbaiki:case ${1:-} in
Raphael
3

gunakan sebuah array.

#!/bin/bash

args=( --flag1 "$1" --flag2 "$2" )
[  "x$3" = xFALSE ] ||  args+=( --optflag1 "$3" )
[  "x$4" = xFALSE ] ||  args+=( --optflag2 "$4" )
[  "x$5" = xFALSE ] ||  args+=( --optflag3 "$5" )
[  "x$6" = xFALSE ] ||  args+=( --optflag4 "$6" )
[  "x$7" = xFALSE ] ||  args+=( --optflag5 "$7" )

program_name "${args[@]}"

ini akan menangani argumen dengan spasi di dalamnya dengan benar.

[sunting] Saya menggunakan sintaks yang kurang lebih sama args=( "${args[@]}" --optflag1 "$3" )tetapi G-Man menyarankan cara yang lebih baik.

Jasen
sumber
1
Anda dapat merampingkan ini sedikit dengan mengatakan args+=( --optflag1 "$3" ). Anda mungkin ingin melihat jawaban saya untuk karya referensi kami, Implikasi keamanan gagal mengutip variabel dalam bash / POSIX shells , di mana saya membahas teknik ini (membangun baris perintah dalam array dengan menambahkan argumen opsional secara kondisional).
G-Man Mengatakan 'Reinstate Monica'
@ G-Man Terima kasih, saya belum melihat bahwa dalam dokumentasi, pemahaman [@]saya memiliki alat yang cukup untuk menyelesaikan masalah saya.
Jasen
1

Dalam skrip shell, argumen adalah "$ 1", "$ 2", "$ 3", dll. Jumlah argumen adalah $ #.
Jika skrip Anda tidak mengenali opsi, Anda dapat mengabaikan deteksi opsi dan memperlakukan semua argumen sebagai operan.
Untuk mengenali opsi-opsi gunakan getop builtin

Michael Durrant
sumber
0

Jika opsi input Anda bersifat posisional (Anda tahu di mana mereka berada), dan tidak ditentukan dengan tanda, maka yang Anda inginkan hanyalah membangun baris perintah. Persiapkan argumen perintah untuk semuanya:

FLAG1="--flag1 $1"
FLAG2="--flag2 $2"
OPTFLAG1=""
OPTFLAG2=""
OPTFLAG3=""
if [ xFALSE != x"$3" ]; then
   OPTFLAG1="--optflag1 $3"
fi
if [ xFALSE != x"$4" ]; then
   OPTFLAG2="--optflag2 $4"
fi
#the same for other flags

#now just run the program
runprogram $FLAG1 $FLAG2 $OPTFLAG1 $OPTFLAG2 $OPTFLAG3

Jika parameter tidak ditentukan, maka string yang sesuai kosong dan berkembang menjadi nol. Perhatikan bahwa di baris terakhir, tidak ada tanda kutip. Itu karena Anda ingin shell untuk membagi parameter menjadi kata-kata (untuk memberikan --flag1dan $1sebagai argumen terpisah untuk program Anda). Tentu saja, ini akan salah jika parameter asli Anda berisi spasi. Jika Anda yang menjalankan ini, maka Anda dapat meninggalkannya, tetapi jika itu adalah skrip umum, ia dapat memiliki perilaku yang tidak terduga jika pengguna memasukkan sesuatu dengan spasi. Untuk mengatasinya, Anda harus membuat kode sedikit lebih buruk.

The xawalan di []uji ada dalam kasus $3atau $4kosong. Dalam hal ini, bash akan berkembang [ FALSE != $3 ]menjadi [ FALSE != ]kesalahan sintaks, jadi ada karakter arbitrer yang ada untuk menjaga hal ini. Ini adalah cara yang sangat umum, Anda akan melihatnya di banyak skrip.

Saya atur OPTFLAG1dan sisanya ""pada awalnya hanya untuk memastikan (kalau-kalau mereka ditetapkan untuk sesuatu sebelumnya), tetapi jika mereka tidak benar-benar dideklarasikan di lingkungan, maka Anda tidak benar-benar perlu melakukan itu.

Beberapa komentar tambahan:

  • Anda sebenarnya hanya dapat menerima parameter dengan cara yang sama seperti runprogram: dengan flags. Itulah yang John Nsedang dibicarakan. Di situlah getoptsmenjadi berguna.
  • Menggunakan FALSE untuk tujuan itu agak tidak standar dan cukup lama. Biasanya, seseorang akan menggunakan karakter tunggal (mungkin -) untuk memberi sinyal nilai kosong, atau hanya meneruskan string kosong, seperti "", jika itu bukan nilai parameter yang valid. String kosong juga membuat tes lebih pendek, cukup gunakan if [ -n "$3" ].
  • Jika hanya ada satu flag opsional, atau jika mereka tergantung sehingga Anda tidak pernah dapat memiliki OPTFLAG2, tetapi tidak OPTFLAG1, maka Anda dapat dengan mudah melewati yang tidak ingin Anda atur. Jika Anda menggunakan ""untuk parameter kosong, seperti yang disarankan di atas, Anda dapat melewatkan semua sisa trailing.

Metode ini cukup banyak cara opsi dilewatkan ke kompiler di Makefiles.

Dan lagi - jika input mungkin mengandung spasi, itu menjadi jelek.

orion
sumber
Tidak, Anda tidak ingin shell membagi parameter menjadi kata-kata. Anda harus selalu mengutip semua referensi ke variabel shell kecuali Anda memiliki alasan yang bagus untuk tidak melakukannya, dan Anda yakin tahu apa yang Anda lakukan. Lihat Implikasi keamanan gagal mengutip variabel dalam bash / POSIX shells , jika Anda tidak terbiasa dengannya, dan gulir ke bawah ke jawaban saya , di mana saya mengatasi masalah ini dengan tepat (dengan teknik yang sama yang digunakan Jasen ).
G-Man Mengatakan 'Reinstate Monica'