Menggunakan getopts untuk memproses opsi baris perintah yang panjang dan pendek

410

Saya ingin memiliki bentuk panjang dan pendek opsi baris perintah dipanggil menggunakan skrip shell saya.

Saya tahu itu getoptsbisa digunakan, tetapi seperti di Perl, saya belum bisa melakukan hal yang sama dengan shell.

Ada ide tentang bagaimana hal ini dapat dilakukan, sehingga saya dapat menggunakan opsi seperti:

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

Di atas, kedua perintah memiliki arti yang sama untuk shell saya, tetapi menggunakan getopts, saya belum bisa mengimplementasikan ini?

gagneet
sumber
2
IMHO, jawaban yang diterima bukan yang terbaik. Itu tidak menunjukkan bagaimana menggunakan getopts untuk menangani argumen "-" dan "-", yang bisa dilakukan, seperti yang diperlihatkan oleh @Arvid Requate. Saya menyisipkan jawaban lain menggunakan konsep serupa, tetapi juga berurusan dengan kesalahan pengguna "lupa" untuk memasukkan nilai untuk argumen yang diperlukan. Poin kunci: getop dapat dibuat bekerja. Pengguna harus menghindari menggunakan "getopt" sebagai gantinya jika portabilitas lintas platform diperlukan. Selain itu, getopts adalah bagian dari standar POSIX untuk kerang, sehingga cenderung portabel.
pauljohn32

Jawaban:

304

Ada tiga implementasi yang dapat dipertimbangkan:

  • Bash builtin getopts. Ini tidak mendukung nama opsi panjang dengan awalan dasbor ganda. Ini hanya mendukung opsi karakter tunggal.

  • BSD UNIX implementasi getoptperintah mandiri (yang digunakan MacOS). Ini juga tidak mendukung opsi panjang.

  • Implementasi mandiri GNU getopt. GNU getopt(3)(digunakan oleh command-line getopt(1)di Linux) mendukung parsing opsi panjang.


Beberapa jawaban lain menunjukkan solusi untuk menggunakan bash builtin getoptsuntuk meniru opsi yang panjang. Solusi itu sebenarnya membuat opsi pendek yang karakternya "-". Jadi Anda mendapatkan "-" sebagai bendera. Lalu apa pun yang mengikuti yang menjadi OPTARG, dan Anda menguji OPTARG dengan bersarang case.

Ini pintar, tetapi disertai dengan peringatan:

  • getoptstidak dapat memberlakukan spec opt. Itu tidak dapat mengembalikan kesalahan jika pengguna memberikan opsi yang tidak valid. Anda harus melakukan pengecekan kesalahan sendiri saat mengurai OPTARG.
  • OPTARG digunakan untuk nama opsi panjang, yang mempersulit penggunaan ketika opsi panjang Anda sendiri memiliki argumen. Anda akhirnya harus kode itu sendiri sebagai kasus tambahan.

Jadi, walaupun dimungkinkan untuk menulis lebih banyak kode untuk mengatasi kurangnya dukungan untuk opsi panjang, ini jauh lebih banyak pekerjaan dan sebagian mengalahkan tujuan menggunakan parser getopt untuk menyederhanakan kode Anda.

Bill Karwin
sumber
18
Begitu. Apa solusi lintas platform, portabel?
troelskn
6
GNU Getopt tampaknya menjadi satu-satunya pilihan. Di Mac, instal GNU getopt dari macports. Di Windows, saya akan menginstal GNU getopt dengan Cygwin.
Bill Karwin
2
Rupanya , ksh getopts dapat menangani opsi yang panjang.
Tgr
1
@ Bill +1, meskipun juga cukup mudah untuk membangun getopt dari sumber ( software.frodo.looijaard.name/getopt ) di Mac. Anda juga dapat memeriksa versi getopt yang diinstal pada sistem Anda dari dalam skrip dengan "getopt -T; echo $?".
Chinasaur
8
@ Bill Karwin: "Bash getopts builtin tidak mendukung nama opsi panjang dengan awalan dasbor ganda." Tetapi getopts dapat dibuat untuk mendukung opsi panjang: lihat stackoverflow.com/a/7680682/915044 di bawah ini.
TomRoche
307

getoptdan getoptsbinatang buas yang berbeda, dan orang-orang tampaknya memiliki sedikit kesalahpahaman tentang apa yang mereka lakukan. getoptsadalah perintah bawaan untuk bashmemroses opsi baris perintah dalam satu lingkaran dan menetapkan masing-masing opsi dan nilai yang ditemukan pada gilirannya ke variabel bawaan, sehingga Anda dapat memprosesnya lebih lanjut. getopt, bagaimanapun, adalah program utilitas eksternal, dan itu tidak benar-benar memproses pilihan Anda untuk Anda seperti yang dilakukan oleh bash getopts, Getoptmodul Perl atau Python optparse/ argparsemodules. Semua yang getoptdilakukan adalah mengkanonik opsi yang diteruskan - yaitu mengonversikannya ke bentuk yang lebih standar, sehingga lebih mudah bagi skrip shell untuk memprosesnya. Misalnya, aplikasi getoptmungkin mengonversi yang berikut:

myscript -ab infile.txt -ooutfile.txt

dalam hal ini:

myscript -a -b -o outfile.txt infile.txt

Anda harus melakukan pemrosesan aktual sendiri. Anda tidak harus menggunakan getoptsama sekali jika Anda membuat berbagai batasan pada cara Anda dapat menentukan opsi:

  • hanya menempatkan satu opsi per argumen;
  • semua opsi berjalan sebelum parameter posisi apa pun (mis. argumen non-opsi);
  • untuk opsi dengan nilai (mis. di -oatas), nilainya harus sebagai argumen terpisah (setelah spasi).

Kenapa menggunakan getoptbukan getopts? Alasan dasarnya adalah hanya GNU yang getoptmemberi Anda dukungan untuk opsi baris perintah yang telah lama dinamai. 1 (GNU getoptadalah default di Linux. Mac OS X dan FreeBSD datang dengan dasar dan tidak terlalu berguna getopt, tetapi versi GNU dapat diinstal; lihat di bawah.)

Sebagai contoh, berikut adalah contoh penggunaan GNU getopt, dari skrip saya yang bernama javawrap:

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Ini memungkinkan Anda menentukan opsi suka --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"atau serupa. Efek dari panggilan ke getoptadalah untuk mengkanoniskan opsi --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"agar Anda dapat memprosesnya dengan lebih mudah. Mengutip sekitar "$1"dan "$2"penting karena memastikan bahwa argumen dengan spasi di dalamnya ditangani dengan benar.

Jika Anda menghapus 9 baris pertama (semuanya naik melalui eval setbaris), kodenya akan tetap berfungsi ! Namun, kode Anda akan lebih pemilih dalam jenis opsi apa yang diterimanya: Khususnya, Anda harus menentukan semua opsi dalam formulir "kanonik" yang dijelaskan di atas. getoptNamun, dengan penggunaan , Anda dapat mengelompokkan opsi huruf tunggal, menggunakan bentuk opsi panjang yang tidak ambigu yang lebih pendek, menggunakan salah satu --file foo.txtatau --file=foo.txtgaya, menggunakan salah satu -m 4096atau -m4096gaya, menggunakan opsi atau gaya, mencampur pilihan dan non-opsi dalam urutan apa pun, dll. getoptjuga menampilkan pesan kesalahan jika opsi tidak dikenal atau ambigu ditemukan.

CATATAN : Sebenarnya ada dua versi yang sangat berbedagetopt , dasar getoptdan GNU getopt, dengan fitur yang berbeda dan konvensi panggilan yang berbeda. 2 Basic getoptcukup rusak: Tidak hanya tidak menangani opsi yang panjang, tetapi juga tidak bisa menangani ruang yang disematkan di dalam argumen atau argumen kosong, sedangkan getoptsmelakukan ini dengan benar. Kode di atas tidak akan berfungsi di dasar getopt. GNU getoptdiinstal secara default di Linux, tetapi pada Mac OS X dan FreeBSD perlu diinstal secara terpisah. Di Mac OS X, instal MacPorts ( http://www.macports.org ) dan kemudian lakukan sudo port install getoptuntuk menginstal GNU getopt(biasanya ke /opt/local/bin), dan pastikan itu /opt/local/binada di jalur shell Anda di depan/usr/bin. Di FreeBSD, instal misc/getopt.

Panduan cepat untuk memodifikasi kode contoh untuk program Anda sendiri: Dari beberapa baris pertama, semua adalah "boilerplate" yang harus tetap sama, kecuali baris yang memanggil getopt. Anda harus mengubah nama program setelah -n, tentukan opsi pendek setelah -o, dan opsi panjang setelah --long. Beri tanda titik dua setelah opsi yang mengambil nilai.

Akhirnya, jika Anda melihat kode yang baru saja setbukaneval set , itu ditulis untuk BSD getopt. Anda harus mengubahnya untuk menggunakan eval setgaya, yang berfungsi dengan baik dengan kedua versi getopt, sementara polos settidak berfungsi dengan baik dengan GNU getopt.

1 Sebenarnya, getoptsdalam ksh93mendukung opsi yang telah lama dinamai, tetapi shell ini tidak digunakan sesering bash. Di zsh, gunakan zparseoptsuntuk mendapatkan fungsi ini.

2 Secara teknis, "GNU getopt" adalah istilah yang salah; versi ini sebenarnya ditulis untuk Linux daripada proyek GNU. Namun, ia mengikuti semua konvensi GNU, dan istilah "GNU getopt" umumnya digunakan (misalnya pada FreeBSD).

Vagabond Perkotaan
sumber
3
Ini sangat membantu, gagasan menggunakan getopt untuk memeriksa opsi dan kemudian memproses opsi-opsi itu dalam satu loop yang sangat sederhana bekerja dengan sangat baik ketika saya ingin menambahkan opsi gaya panjang ke skrip bash. Terima kasih.
ianmjones
2
getoptdi Linux bukan utilitas GNU dan tradisional getopttidak awalnya berasal dari BSD tetapi dari AT&T Unix. ksh93 getopts(juga dari AT&T) mendukung opsi panjang bergaya GNU.
Stephane Chazelas
@StephaneChazelas - diedit untuk mencerminkan komentar Anda. Saya masih lebih suka istilah "GNU getopt" walaupun itu keliru, karena versi ini mengikuti konvensi GNU dan umumnya bertindak seperti program GNU (misalnya memanfaatkan POSIXLY_CORRECT), sementara "Linux-enhanced getopt" secara salah menunjukkan bahwa versi ini hanya ada di Linux.
Urban Vagabond
1
Itu berasal dari paket util-linux, jadi itu hanya Linux karena bundel perangkat lunak itu dimaksudkan hanya untuk Linux (yang getoptbisa dengan mudah diporting ke Unix lain, tetapi banyak perangkat lunak lain util-linuxyang khusus untuk Linux). Semua program non-GNU yang memanfaatkan GNU getopt (3) mengerti $POSIX_CORRECT. Misalnya, Anda tidak akan mengatakan itu aplayGNU hanya dengan alasan itu. Saya menduga bahwa ketika FreeBSD menyebutkan GNU getopt, artinya GNU getopt (3) C API.
Stephane Chazelas
@StephaneChazelas - FreeBSD memiliki pesan kesalahan "Bangun ketergantungan: Silakan instal GNU getopt" yang secara jelas merujuk pada getoptutil, bukan getopt (3).
Urban Vagabond
202

Fungsi getash bawaan Bash dapat digunakan untuk mem-parsing opsi-opsi panjang dengan meletakkan karakter tanda hubung diikuti oleh titik dua ke dalam optspec:

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

Setelah menyalin ke nama file yang dapat dieksekusi = getopts_test.shdi direktori kerja saat ini , orang dapat menghasilkan output seperti

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

Jelas getopts tidak melakukan OPTERRpemeriksaan atau penguraian opsi-argumen untuk opsi yang panjang. Fragmen skrip di atas menunjukkan bagaimana hal ini dapat dilakukan secara manual. Prinsip dasar juga bekerja di shell Debian Almquist ("dash"). Perhatikan kasus khusus:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

Perhatikan bahwa, seperti yang ditunjukkan GreyCat dari http://mywiki.wooledge.org/BashFAQ , trik ini mengeksploitasi perilaku non-standar dari shell yang memungkinkan opsi-argumen (yaitu nama file dalam "-f nama file") untuk digabungkan dengan opsi (seperti dalam "-filename"). Standar POSIX mengatakan harus ada ruang di antara mereka, yang dalam kasus "- longoption" akan mengakhiri parsing opsi dan mengubah semua longoptions menjadi argumen non-opsi.

Requate Arvid
sumber
2
Satu pertanyaan: apa adalah semantik !di val="${!OPTIND}?
TomRoche
2
@TomRoche itu adalah Pergantian Tidak Langsung: unix.stackexchange.com/a/41293/84316
ecbrodie
2
@ecbrodie: Itu karena dua argumen sebenarnya telah diproses, bukan hanya satu. Argumen pertama adalah kata "loglevel", dan berikutnya adalah argumen untuk itu argumen. Sementara itu, getoptssecara otomatis hanya menambah OPTINDdengan 1, tetapi dalam kasus kami, kami memerlukannya untuk menambah 2, jadi kami menambahnya dengan 1 secara manual, lalu membiarkannya getoptsbertambah 1 lagi untuk kami secara otomatis.
Victor Zamanian
3
Karena kita berada dalam kesetimbangan bash di sini: Nama-nama variabel telanjang diizinkan di dalam ekspresi aritmatika, tidak $perlu. OPTIND=$(( $OPTIND + 1 ))bisa adil OPTIND=$(( OPTIND + 1 )). Yang lebih menarik lagi, Anda bahkan dapat menetapkan dan meningkatkan variabel di dalam ekspresi aritmatika, sehingga dimungkinkan untuk menyingkatnya lebih jauh : $(( ++OPTIND )), atau bahkan (( ++OPTIND ))mempertimbangkan yang ++OPTINDakan selalu positif, sehingga tidak akan meningkatkan shell run dengan -eopsi. :-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
clacke
3
Kenapa tidak --very-badmemberi peringatan?
Tom Hale
148

Built-in getopts command masih, AFAIK, terbatas pada pilihan karakter tunggal saja.

Ada (atau dulu) program eksternal getoptyang akan mengatur ulang serangkaian opsi sedemikian rupa sehingga lebih mudah diurai. Anda dapat menyesuaikan desain itu untuk menangani opsi yang panjang juga. Contoh penggunaan:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

Anda dapat menggunakan skema serupa dengan a getoptlong perintah.

Perhatikan bahwa kelemahan mendasar dengan getoptprogram eksternal adalah sulitnya menangani argumen dengan spasi di dalamnya, dan dalam menjaga ruang-ruang tersebut secara akurat. Inilah sebabnya mengapa built-in getoptslebih unggul, meskipun dibatasi oleh fakta bahwa itu hanya menangani opsi huruf tunggal.

Jonathan Leffler
sumber
11
getopt, kecuali untuk versi GNU (yang memiliki konvensi panggilan yang berbeda), pada dasarnya rusak. Jangan gunakan itu. Silakan gunakan ** getopts sebagai gantinya bash-hackers.org/wiki/doku.php/howto/getopts_tutorial
hendry
9
@endry - dari tautan Anda sendiri: "Perhatikan bahwa getopts tidak dapat menguraikan opsi panjang gaya GNU (--myoption) atau opsi panjang gaya XF86 (-myoption)!"
Tom Auger
1
Jonathan - Anda harus menulis ulang contoh untuk digunakan eval setdengan tanda kutip (lihat jawaban saya di bawah) sehingga juga bekerja dengan benar dengan GNU getopt (default di Linux) dan menangani spasi dengan benar.
Urban Vagabond
@UrbanVagabond: Saya tidak yakin mengapa saya harus melakukan itu. Pertanyaannya ditandai Unix, bukan Linux. Saya menunjukkan mekanisme tradisional, dengan sengaja, dan memiliki masalah dengan kekosongan dalam argumen, dll. Anda dapat mendemonstrasikan versi spesifik Linux modern jika Anda mau, dan jawaban Anda melakukannya. (Saya perhatikan, passim, bahwa penggunaan Anda ${1+"$@"}aneh dan bertentangan dengan apa yang diperlukan dalam shell modern dan khususnya dengan shell apa pun yang Anda temukan di Linux. Lihat Menggunakan $ 1: + "$ @"} di / bin / sh untuk pembahasan notasi itu.)
Jonathan Leffler
Anda harus melakukannya karena eval setmelakukan hal yang benar dengan GNU dan BSD getopt, sedangkan plain sethanya melakukan hal yang benar dengan BSD getopt. Jadi sebaiknya Anda gunakan eval setuntuk mendorong orang agar terbiasa melakukan hal ini. Terima kasih BTW, saya tidak menyadari bahwa ${1+"$@"}itu tidak diperlukan lagi. Saya harus menulis hal-hal yang berfungsi baik di Mac OS X dan Linux - di antara keduanya, mereka memaksa banyak portabilitas. Aku hanya memeriksa dan "$@"memang melakukan hal yang benar pada semua sh, bash, ksh, dan zshdi bawah Mac OS X; pasti di Linux juga.
Urban Vagabond
78

Berikut ini contoh yang benar-benar menggunakan getopt dengan opsi panjang:

aflag=no
bflag=no
cargument=none

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done
sme
sumber
1
Anda harus menulis ulang contoh untuk digunakan eval setdengan tanda kutip (lihat jawaban saya di bawah) sehingga ini juga berfungsi dengan benar dengan GNU getopt (default di Linux) dan menangani spasi dengan benar.
Urban Vagabond
2
Ini menggunakan getoptsementara pertanyaannya adalah tentang getopts.
Niklas Berglund
1
Apakah (--, (-*dan (*pola yang valid? Bagaimana mereka berbeda --, -*dan *?
Maëlan
1
@ Maëlan - The kurung buka terkemuka adalah opsional, sehingga (--)identik dengan --)dalam casebait. Sungguh aneh melihat lekukan yang tidak rata dan penggunaan yang tidak konsisten dari parensa pilihan opsional, tetapi kode jawaban saat ini terlihat valid bagi saya.
Adam Katz
59

Opsi panjang dapat diuraikan oleh standar getoptsbawaan sebagai "argumen" ke -"opsi"

Ini adalah shell POSIX portabel dan asli - tidak diperlukan program eksternal atau bashisme.

Ini panduan alat pilihan selama argumen dengan -pilihan, sehingga --alphadipandang oleh getoptskarena -dengan argumen alphadan --bravo=foodipandang sebagai -dengan argumen bravo=foo. Argumen benar dapat dipanen dengan penggantian sederhana: ${OPTARG#*=}.

Dalam contoh ini, -bdan -c(dan bentuknya yang panjang, --bravodan --charlie) memiliki argumen wajib. Argumen untuk opsi panjang datang setelah tanda sama dengan, misalnya --bravo=foo(pembatas ruang untuk opsi panjang akan sulit untuk diterapkan, lihat di bawah).

Karena ini menggunakan getoptsbuiltin , solusi ini mendukung penggunaan seperti cmd --bravo=foo -ac FILE(yang memiliki opsi gabungan -adan -cdan interleave opsi panjang dengan opsi standar) sementara sebagian besar jawaban lain di sini berjuang atau gagal melakukannya.

die() { echo "$*" >&2; exit 2; }  # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

while getopts ab:c:-: OPT; do
  # support long options: https://stackoverflow.com/a/28466267/519360
  if [ "$OPT" = "-" ]; then   # long option: reformulate OPT and OPTARG
    OPT="${OPTARG%%=*}"       # extract long option name
    OPTARG="${OPTARG#$OPT}"   # extract long option argument (may be empty)
    OPTARG="${OPTARG#=}"      # if long option argument, remove assigning `=`
  fi
  case "$OPT" in
    a | alpha )    alpha=true ;;
    b | bravo )    needs_arg; bravo="$OPTARG" ;;
    c | charlie )  needs_arg; charlie="$OPTARG" ;;
    ??* )          die "Illegal option --$OPT" ;;  # bad long option
    \? )           exit 2 ;;  # bad short option (error reported via getopts)
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

Ketika opsi adalah tanda hubung ( -), itu adalah opsi yang panjang. getoptsakan mem-parsing opsi panjang aktual ke $OPTARG, misalnya --bravo=fooset awal OPT='-'dan OPTARG='bravo=foo'. The ifbait set $OPTdengan isi $OPTARGsebelum equals tanda pertama ( bravodalam contoh kita) dan kemudian menghapus yang dari awal $OPTARG(menghasilkan =foodalam langkah ini, atau string kosong jika tidak ada =). Akhirnya, kami menghapus argumen yang utama =. Pada titik ini, $OPTada opsi pendek (satu karakter) atau opsi panjang (2+ karakter).

The casekemudian cocok baik pendek atau opsi panjang. Untuk opsi pendek, getoptssecara otomatis mengeluh tentang opsi dan argumen yang hilang, jadi kami harus mereplikasi mereka secara manual menggunakan needs_argfungsi, yang secara fatal keluar ketika $OPTARGkosong. The ??*Kondisi akan ditemukan sisa opsi panjang ( ?cocok dengan karakter tunggal dan *pertandingan nol atau lebih, sehingga ??*cocok 2+ karakter), memungkinkan kita untuk mengeluarkan error "pilihan Ilegal" sebelum keluar.

(Catatan tentang semua nama variabel huruf besar: Secara umum, sarannya adalah untuk mencadangkan semua variabel huruf besar untuk penggunaan sistem. Saya tetap $OPTmenggunakan huruf besar semua untuk tetap sejalan $OPTARG, tapi ini melanggar konvensi itu. Saya pikir itu cocok karena ini adalah sesuatu yang seharusnya dilakukan sistem, dan harus aman karena tidak ada standar (afaik) yang menggunakan variabel seperti itu.)


Untuk mengeluh tentang argumen tak terduga ke opsi panjang, meniru apa yang kami lakukan untuk argumen wajib: gunakan fungsi pembantu. Balik saja tes untuk mengeluh tentang argumen ketika seseorang tidak diharapkan:

no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }

Versi yang lebih lama dari jawaban ini berupaya menerima opsi panjang dengan argumen terbatas-ruang, tetapi tidak dapat diandalkan; getoptsbisa secara prematur berakhir dengan asumsi bahwa argumen itu di luar cakupannya dan penambahan secara manual $OPTINDtidak berfungsi di semua shell.

Ini akan dicapai dengan menggunakan salah satu dari teknik ini:

dan kemudian menyimpulkan dengan sesuatu seperti [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))

Adam Katz
sumber
Solusi mandiri yang sangat bagus. Satu pertanyaan: Karena letter-ctidak memerlukan argumen, apakah itu tidak cukup untuk digunakan letter-c)? yang *tampaknya berlebihan.
Philip Kearns
1
@ Argumen Posisi adalah UX yang buruk; mereka sulit dimengerti dan argumen opsional cukup berantakan. getoptsberhenti pada argumen posisi pertama karena tidak dirancang untuk menghadapinya. Ini memungkinkan sub-perintah dengan argumen mereka sendiri, misalnya git diff --color, jadi saya akan menafsirkan command --foo=moo bar --baz wazsebagai memiliki --fooargumen commanddan --baz wazsebagai argumen (dengan opsi) ke barsub-perintah. Ini dapat dilakukan dengan kode di atas. Saya menolak --bravo -blahkarena --bravomembutuhkan argumen dan tidak jelas bahwa -blahitu bukan pilihan lain.
Adam Katz
1
Saya tidak setuju tentang UX: argumen posisi berguna dan mudah, selama Anda membatasi jumlah mereka (paling banyak 2 atau 1 ditambah N-of-the-type-sama). Seharusnya dimungkinkan untuk menyelingi mereka dengan argumen kata kunci, karena pengguna kemudian dapat membangun perintah langkah demi langkah (yaitu ls abc -la).
Arne Babenhauserheide
1
@AdamKatz: Saya menulis artikel kecil dengan ini: draketo.de/english/free-software/shell-argument-parsing - termasuk pembacaan berulang argumen yang tersisa untuk menangkap opsi tambahan.
Arne Babenhauserheide
1
@ArneBabenhauserheide: Saya telah memperbarui jawaban ini untuk mendukung argumen yang dibatasi ruang. Karena memerlukan evaldalam shell POSIX, itu terdaftar di bawah sisa jawabannya.
Adam Katz
33

Lihatlah shFlags yang merupakan pustaka shell portabel (artinya: sh, bash, dash, ksh, zsh di Linux, Solaris, dll.).

Itu membuat menambahkan bendera baru sesederhana menambahkan satu baris ke skrip Anda, dan menyediakan fungsi penggunaan yang dihasilkan secara otomatis.

Berikut adalah shFlag sederhana yang Hello, world!digunakan :

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

Untuk OS yang memiliki getopt yang disempurnakan yang mendukung opsi panjang (misalnya Linux), Anda dapat melakukan:

$ ./hello_world.sh --name Kate
Hello, Kate!

Untuk sisanya, Anda harus menggunakan opsi pendek:

$ ./hello_world.sh -n Kate
Hello, Kate!

Menambahkan bendera baru semudah menambahkan bendera baru DEFINE_ call.

k0pernikus
sumber
2
Ini fantastis tetapi sayangnya getopt saya (OS X) tidak mendukung spasi dalam argumen: / bertanya-tanya apakah ada alternatif.
Alastair Stuart
@AlastairStuart - memang ada alternatif di OS X. Gunakan MacPorts untuk menginstal GNU getopt (biasanya akan diinstal ke / opt / local / bin / getopt).
Urban Vagabond
3
@UrbanVagabond - pemasangan alat non-sistem default sayangnya bukan persyaratan yang dapat diterima untuk alat yang cukup portabel.
Alastair Stuart
@AlastairStuart - lihat jawaban saya untuk solusi portabel yang menggunakan getop builtin daripada GNU getopt. Ini sama dengan penggunaan getop dasar tetapi dengan iterasi ekstra untuk opsi panjang.
Adam Katz
31

Menggunakan getoptsdengan opsi / argumen pendek / panjang


Bekerja dengan semua kombinasi, misalnya:

  • foobar -f --bar
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB --arguments = longhorn
  • foobar -fA "text shorty" -B --arguments = "text longhorn"
  • bash foobar -F --barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

Beberapa deklarasi untuk contoh ini

Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

Bagaimana fungsi Penggunaan akan terlihat

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
  $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                  -f   --foo            Set foo to yes    ($bart)
                  -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

getops dengan bendera panjang / pendek serta argumen panjang

while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

Keluaran

##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"

Menggabungkan hal-hal di atas menjadi naskah yang kohesif

#!/bin/bash
# foobar: getopts with short and long options AND arguments

function _cleanup ()
{
  unset -f _usage _cleanup ; return 0
}

## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN

###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
   $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                    -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

##################################################################    
#######  "getopts" with: short options  AND  long options  #######
#######            AND  short/long arguments               #######
while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
               --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done
RapaNui
sumber
Apakah ini tidak berfungsi dengan lebih dari satu argumen panjang (-). Sepertinya hanya membaca yang pertama untuk saya.
Sinaesthetic
@Sinesthetic - Ya, saya bermain dengan evalpendekatan untuk argumen spasi pada opsi panjang dan merasa tidak dapat diandalkan dengan shell tertentu (meskipun saya berharap ini akan bekerja dengan bash, dalam hal ini Anda tidak perlu menggunakan eval). Lihat jawaban saya untuk cara menerima argumen opsi panjang =dan upaya saya yang dicatat untuk menggunakan ruang. Solusi saya tidak membuat panggilan eksternal sementara yang ini menggunakan cutbeberapa kali.
Adam Katz
24

Cara lain...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done
mtvee
sumber
1
Tidakkah ini membutuhkan ruang dalam setiap $argspenugasan kembali? Ini bahkan bisa dilakukan tanpa bashism, tetapi kode ini akan kehilangan ruang dalam opsi dan argumen (saya pikir $delimtriknya tidak akan berhasil). Anda bisa menjalankannya set di dalam forloop jika cukup hati-hati untuk mengosongkannya hanya pada iterasi pertama. Ini adalah versi yang lebih aman tanpa bashism.
Adam Katz
18

Saya semacam dipecahkan dengan cara ini:

# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

Apakah saya bodoh atau semacamnya? getoptdan getoptssangat membingungkan.

Benjamin
sumber
1
Ini sepertinya bekerja untuk saya, saya tidak tahu apa masalahnya dengan metode ini, tetapi tampaknya sederhana, jadi pasti ada alasan semua orang tidak menggunakannya.
Billy Moon
1
@ Billy Ya, ini sederhana karena saya tidak menggunakan skrip apa pun untuk mengelola parameter saya dan lain-lain. Pada dasarnya saya mengonversi string argumen ($ @) ke sebuah array dan saya mengulanginya. Dalam loop, nilai saat ini akan menjadi kunci dan yang berikutnya adalah nilai. Sederhana seperti itu.
1
@ Theodore Saya senang ini membantu Anda! Itu juga menyakitkan bagiku. Jika Anda tertarik, Anda dapat melihat contohnya beraksi di sini: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh
2
Jelas cara termudah yang pernah saya lihat. Saya mengubahnya sedikit seperti menggunakan i = $ (($ i +1)) daripada expr tetapi konsepnya kedap udara.
Thomas Dignan
6
Anda tidak bodoh sama sekali, tetapi Anda mungkin kehilangan fitur: getopt (s) dapat mengenali opsi yang dicampur (mis: -ltratau -lt -rjuga -l -t -r). Dan itu juga menyediakan beberapa penanganan kesalahan, dan cara mudah untuk memindahkan parameter yang dirawat begitu opsi perawatan selesai.
Olivier Dulac
14

Jika Anda tidak menginginkannya getopt ketergantungan, Anda dapat melakukan ini:

while test $# -gt 0
do
  case $1 in

  # Normal option processing
    -h | --help)
      # usage and help
      ;;
    -v | --version)
      # version info
      ;;
  # ...

  # Special cases
    --)
      break
      ;;
    --*)
      # error unknown (long) option $1
      ;;
    -?)
      # error unknown (short) option $1
      ;;

  # FUN STUFF HERE:
  # Split apart combined short options
    -*)
      split=$1
      shift
      set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

Tentu saja, maka Anda tidak dapat menggunakan opsi gaya panjang dengan satu tanda hubung. Dan jika Anda ingin menambahkan versi singkat (mis. --Verbos bukan --verbose), maka Anda perlu menambahkannya secara manual.

Tetapi jika Anda mencari untuk mendapatkan getoptsfungsionalitas bersama dengan opsi panjang, ini adalah cara sederhana untuk melakukannya.

Saya juga menaruh cuplikan ini di intinya .

jakesandlund
sumber
Ini sepertinya hanya bekerja dengan satu opsi panjang pada satu waktu, tetapi memenuhi kebutuhan saya. Terima kasih!
kingjeffrey
Dalam kasus khusus --)sepertinya ada yang shift ;hilang. Saat ini --argumen tersebut akan tetap sebagai argumen non opsi pertama.
dgw
Saya pikir ini sebenarnya jawaban yang lebih baik, meskipun sebagai dgw menunjukkan --opsi perlu shiftdi sana. Saya mengatakan ini lebih baik karena alternatifnya adalah versi tergantung platform getoptatau getopts_longatau Anda harus memaksa opsi pendek untuk digunakan hanya pada awal perintah (yaitu - Anda menggunakan getoptskemudian memproses opsi lama setelah itu), sedangkan ini memberikan perintah apa pun dan kontrol penuh.
Haravikk
Jawaban ini membuat saya bertanya-tanya mengapa kami memiliki puluhan jawaban untuk melakukan pekerjaan yang dapat dilakukan dengan tidak lebih dari solusi yang benar-benar jelas dan langsung ini , dan jika ada alasan untuk miliar getop (s) menggunakan kasus selain membuktikan diri.
Florian Heigl
11

Built-in getoptstidak dapat melakukan ini. Ada program getopt eksternal (1) yang dapat melakukan ini, tetapi Anda hanya mendapatkannya di Linux dari paket util-linux . Itu datang dengan contoh skrip getopt-parse.bash .

Ada juga yang getopts_longditulis sebagai fungsi shell.

Nietzche-jou
sumber
3
Itu getopttermasuk dalam versi FreeBSD 1.0 pada tahun 1993, dan telah menjadi bagian dari FreeBSD sejak saat itu. Dengan demikian, ini diadopsi dari FreeBSD 4.x untuk dimasukkan dalam proyek Darwin Apple. Pada OS X 10.6.8, halaman manual yang disertakan oleh Apple tetap merupakan duplikat halaman manual FreeBSD. Jadi ya, itu termasuk dalam OS X dan sekumpulan sistem operasi lain di samping Linux. -1 pada jawaban ini untuk informasi yang salah.
ghoti
8
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

.

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit
3ED
sumber
2
Penjelasan akan menyenangkan. Skrip pertama hanya menerima opsi pendek sementara skrip kedua memiliki bug dalam penguraian argumen opsi panjangnya; variabelnya harus "${1:0:1}"untuk argumen # 1, substring pada indeks 0, panjang 1. Ini tidak mengizinkan pencampuran opsi pendek dan panjang.
Adam Katz
7

Dalam ksh93, getoptsapakah mendukung nama panjang ...

while getopts "f(file):s(server):" flag
do
    echo "$flag" $OPTIND $OPTARG
done

Atau begitulah tutorial yang saya temukan mengatakan. Cobalah dan lihatlah.

Richard Lynch
sumber
4
Ini adalah bawaan ksh93. Terlepas dari sintaks ini, ia juga memiliki sintaksis yang lebih rumit yang juga memungkinkan opsi panjang tanpa padanan pendek, dan banyak lagi.
jilles
2
Jawaban yang masuk akal. OP tidak menentukan shell APA.
ghoti
6

Saya hanya menulis skrip shell sekarang dan kemudian dan keluar dari latihan, sehingga umpan balik sangat dihargai.

Menggunakan strategi yang diusulkan oleh @Arvid Requate, kami menemukan beberapa kesalahan pengguna. Seorang pengguna yang lupa untuk memasukkan nilai secara tidak sengaja akan membuat nama opsi berikutnya diperlakukan sebagai nilai:

./getopts_test.sh --loglevel= --toc=TRUE

akan menyebabkan nilai "loglevel" dilihat sebagai "--toc = TRUE". Ini bisa dihindari.

Saya mengadaptasi beberapa ide tentang memeriksa kesalahan pengguna untuk CLI dari http://mwiki.wooledge.org/BashFAQ/035 diskusi parsing manual. Saya memasukkan kesalahan saat menangani argumen "-" dan "-".

Lalu saya mulai mengutak-atik sintaksis, jadi kesalahan apa pun di sini sepenuhnya salah saya, bukan penulis asli.

Pendekatan saya membantu pengguna yang lebih suka masuk lama dengan atau tanpa tanda sama dengan. Artinya, ia harus memiliki respons yang sama terhadap "--loglevel 9" dengan "--loglevel = 9". Dalam metode - / space, tidak mungkin untuk mengetahui dengan pasti apakah pengguna lupa argumen, sehingga beberapa tebakan diperlukan.

  1. jika pengguna memiliki format tanda panjang / sama (--opt =), maka spasi setelah = memicu kesalahan karena argumen tidak disediakan.
  2. jika pengguna memiliki argumen panjang / spasi (--opt), skrip ini menyebabkan kegagalan jika tidak ada argumen yang mengikuti (akhir perintah) atau jika argumen dimulai dengan tanda hubung)

Jika Anda memulai ini, ada perbedaan yang menarik antara format "--opt = value" dan "--opt value". Dengan tanda sama dengan, argumen baris perintah dilihat sebagai "opt = value" dan pekerjaan untuk menangani itu adalah parsing string, untuk memisahkan pada "=". Sebaliknya, dengan "--opt value", nama argumennya adalah "opt" dan kami memiliki tantangan untuk mendapatkan nilai berikutnya yang disediakan di baris perintah. Di situlah @Arvid Requate menggunakan $ {! OPTIND}, referensi tidak langsung. Saya masih tidak mengerti itu, well, sama sekali, dan komentar di BashFAQ tampaknya memperingatkan terhadap gaya itu ( http://mywiki.wooledge.org/BashFAQ/006 ). BTW, saya tidak berpikir komentar poster sebelumnya tentang pentingnya OPTIND = $ (($ OPTIND + 1)) benar. Maksud saya mengatakan,

Dalam versi terbaru dari skrip ini, flag -v berarti cetakan VERBOSE.

Simpan dalam file yang disebut "cli-5.sh", buat dapat dieksekusi, dan semua ini akan berfungsi, atau gagal dengan cara yang diinginkan

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

Berikut adalah contoh keluaran dari pengecekan kesalahan pada intpu pengguna

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

Anda harus mempertimbangkan mengaktifkan -v, karena mencetak internal OPTIND dan OPTARG

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


die() {
    printf '%s\n' "$1" >&2
    exit 1
}

printparse(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
    fi
}

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "$1" >&2;
    fi
}


VERBOSE=0
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"
pauljohn32
sumber
OPTIND=$(( $OPTIND + 1 )): diperlukan setiap kali Anda 'melahap' parameter OPTIND (untuk contoh: ketika yang digunakan --toc value : nilai dalam nomor parameter $ OPTIND. Setelah Anda mengambilnya untuk nilai toc, Anda harus memberi tahu getopts bahwa parameter berikutnya yang diurai bukan nilai, tetapi satu setelah itu (maka:. OPTIND=$(( $OPTIND + 1 )) dan skrip Anda (serta skrip yang Anda rujuk) tidak ada, setelah selesai: shift $(( $OPTIND -1 ))(saat getop keluar setelah parsing parameterrs 1 ke OPTIND-1, Anda perlu menggesernya begitu $@sekarang menjadi parameter "non-opsi" yang tersisa
Olivier Dulac
oh, ketika Anda menggeser diri Anda, Anda "menggeser" parameter di bawah getop, jadi OPTIND selalu menunjuk hal yang benar ... tapi saya merasa sangat membingungkan. Saya percaya (tidak dapat menguji skrip Anda sekarang) bahwa Anda masih memerlukan shift $ (($ OPTIND - 1)) setelah getop while, sehingga $ 1 sekarang tidak menunjuk ke $ 1 asli (sebuah opsi) tetapi ke yang pertama dari argumen yang tersisa (yang datang setelah semua opsi dan nilainya). mis: myrm -foo -bar = baz thisarg thenthisone thenanother
Olivier Dulac
5

Menemukan versi lain dari roda ...

Fungsi ini adalah (mudah-mudahan) POSIX-kompatibel pengganti shell bourne untuk GNU getopt. Ini mendukung opsi pendek / panjang yang dapat menerima argumen wajib / opsional / tidak, dan cara menentukan opsi hampir identik dengan GNU getopt, sehingga konversi sepele.

Tentu saja ini masih merupakan potongan kode yang cukup besar untuk dimasukkan ke dalam skrip, tetapi ini sekitar setengah baris dari fungsi shell getopt_long yang terkenal, dan mungkin lebih disukai dalam kasus di mana Anda hanya ingin mengganti penggunaan getopt GNU yang ada.

Ini adalah kode yang cukup baru, jadi YMMV (dan tentu saja tolong beri tahu saya jika ini sebenarnya tidak kompatibel dengan POSIX untuk alasan apa pun - portabilitas adalah niat sejak awal, tetapi saya tidak memiliki lingkungan pengujian POSIX yang berguna).

Kode dan contoh penggunaannya sebagai berikut:

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

Contoh penggunaan:

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi
phils
sumber
4

Jawaban yang diterima melakukan pekerjaan yang sangat baik untuk menunjukkan semua kekurangan bash built-in getopts. Jawabannya berakhir dengan:

Jadi, walaupun dimungkinkan untuk menulis lebih banyak kode untuk mengatasi kurangnya dukungan untuk opsi panjang, ini jauh lebih banyak pekerjaan dan sebagian mengalahkan tujuan menggunakan parser getopt untuk menyederhanakan kode Anda.

Dan meskipun saya setuju secara prinsip dengan pernyataan itu, saya merasa bahwa berapa kali kita semua menerapkan fitur ini dalam berbagai skrip membenarkan upaya untuk menciptakan solusi "standar", teruji dengan baik.

Karena itu, saya telah "memutakhirkan" bash yang dibangun getoptsdengan menerapkan getopts_longbash murni, tanpa ketergantungan eksternal. Penggunaan fungsi ini 100% kompatibel dengan built-in getopts.

Dengan memasukkan getopts_long(yang di- host di GitHub ) dalam sebuah skrip, jawaban atas pertanyaan awal dapat diimplementasikan sesederhana:

source "${PATH_TO}/getopts_long.bash"

while getopts_long ':c: copyfile:' OPTKEY; do
    case ${OPTKEY} in
        'c'|'copyfile')
            echo 'file supplied -- ${OPTARG}'
            ;;
        '?')
            echo "INVALID OPTION -- ${OPTARG}" >&2
            exit 1
            ;;
        ':')
            echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
            exit 1
            ;;
        *)
            echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
            exit 1
            ;;
    esac
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift
UrsaDK
sumber
3

Saya belum memiliki cukup perwakilan untuk mengomentari atau memilih solusinya, tetapi jawaban sme bekerja sangat baik untuk saya. Satu-satunya masalah yang saya temui adalah bahwa argumen akhirnya dibungkus dengan tanda kutip tunggal (jadi saya harus menghapusnya).

Saya juga menambahkan beberapa contoh penggunaan dan BANTUAN teks. Saya akan menyertakan versi saya yang sedikit diperpanjang di sini:

#!/bin/bash

# getopt example
# from: /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done
kghastie
sumber
3

Di sini Anda dapat menemukan beberapa pendekatan berbeda untuk penguraian opsi kompleks di bash: http://mywiki.wooledge.org/ComplexOptionParsing

Saya memang membuat yang berikut ini, dan saya pikir ini bagus, karena kode minimal dan opsi panjang dan pendek berfungsi. Opsi panjang juga dapat memiliki beberapa argumen dengan pendekatan ini.

#!/bin/bash
# Uses bash extensions.  Not portable as written.

declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
    case "${opt}" in
        -) #OPTARG is name-of-long-option or name-of-long-option=value
            if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
            then
                opt=${OPTARG/=*/}
                OPTARG=${OPTARG#*=}
                ((OPTIND--))    
            else #with this --key value1 value2 format multiple arguments are possible
                opt="$OPTARG"
                OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
            fi
            ((OPTIND+=longoptspec[$opt]))
            continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
            ;;
        loglevel)
          loglevel=$OPTARG
            ;;
        h|help)
            echo "usage: $0 [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file
pengguna3573558
sumber
2

Saya telah mengerjakan hal itu untuk waktu yang cukup lama ... dan membuat perpustakaan saya sendiri yang Anda perlu sumber dalam naskah utama Anda. Lihat libopt4shell dan cd2mpc untuk contohnya. Semoga ini bisa membantu!

tulang belakang
sumber
2

Solusi yang ditingkatkan:

# translate long options to short
# Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after  "--" in option fields.
for ((i=1;$#;i++)) ; do
    case "$1" in
        --)
            # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
            EndOpt=1 ;;&
        --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
        # default case : short option use the first char of the long option:
        --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
        # pass through anything else:
        *) args[$i]="$1" ;;
    esac
    shift
done
# reset the translated args
set -- "${args[@]}"

function usage {
echo "Usage: $0 [options] files" >&2
    exit $1
}

# now we can process with getopt
while getopts ":hvVc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        V)  echo $Version ; exit ;;
        c)  source $OPTARG ;;
        \?) echo "unrecognized option: -$opt" ; usage -1 ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage -1
        ;;
    esac
done

shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift
jbar
sumber
2

Mungkin lebih mudah menggunakan ksh, hanya untuk bagian getopts, jika perlu opsi baris perintah yang panjang, karena bisa lebih mudah dilakukan di sana.

# Working Getopts Long => KSH

#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done
efstathiou_e
sumber
+1 - Perhatikan bahwa ini terbatas pada ksh93 - dari proyek AST open source (AT&T Research).
Henk Langeveld
2

Saya menginginkan sesuatu tanpa ketergantungan eksternal, dengan dukungan bash yang ketat (-u), dan saya membutuhkannya untuk bekerja pada versi bash yang lebih lama. Ini menangani berbagai jenis params:

  • short bools (-h)
  • opsi pendek (-i "image.jpg")
  • long bools (--help)
  • sama dengan opsi (--file = "filename.ext")
  • opsi ruang (--file "filename.ext")
  • bools concatinated (-hvm)

Cukup masukkan yang berikut di bagian atas skrip Anda:

# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
  for param in $1 ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "$2" ]] ; then
        # Update the key to match the long version
        local arr=(${param//\|/ })
        let last=${#arr[@]}-1
        key="${arr[$last]}"
        return 0
      fi
    done
  done
  return 1
}

# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
#     # First, set your defaults
#     param_help=false
#     param_path="."
#     param_file=false
#     param_image=false
#     param_image_lossy=true
#     # Define allowed parameters
#     allowed_params="h|?|help p|path f|file i|image image-lossy"
#     # Get parameters from the arguments provided
#     _get_params $*
#
# Parameters will be converted into safe variable names like:
#     param_help,
#     param_path,
#     param_file,
#     param_image,
#     param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
#     -i "path/goes/here"
#     --image "path/goes/here"
#     --image="path/goes/here"
#     --image=path/goes/here
# These would all result in effectively the same thing:
#     param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
#     -vhm is the same as -v -h -m
_get_params(){

  local param_pair
  local key
  local value
  local shift_count

  while : ; do
    # Ensure we have a valid param. Allows this to work even in -u mode.
    if [[ $# == 0 || -z $1 ]] ; then
      break
    fi

    # Split the argument if it contains "="
    param_pair=(${1//=/ })
    # Remove preceeding dashes
    key="${param_pair[0]#--}"

    # Check for concatinated boolean short parameters.
    local nodash="${key#-}"
    local breakout=false
    if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
      # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
      local short_param_count=${#nodash}
      let new_arg_count=$#+$short_param_count-1
      local new_args=""
      # $str_pos is the current position in the short param string $nodash
      for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
        # The first character becomes the current key
        if [ $str_pos -eq 0 ] ; then
          key="${nodash:$str_pos:1}"
          breakout=true
        fi
        # $arg_pos is the current position in the constructed arguments list
        let arg_pos=$str_pos+1
        if [ $arg_pos -gt $short_param_count ] ; then
          # handle other arguments
          let orignal_arg_number=$arg_pos-$short_param_count+1
          local new_arg="${!orignal_arg_number}"
        else
          # break out our one argument into new ones
          local new_arg="-${nodash:$str_pos:1}"
        fi
        new_args="$new_args \"$new_arg\""
      done
      # remove the preceding space and set the new arguments
      eval set -- "${new_args# }"
    fi
    if ! $breakout ; then
      key="$nodash"
    fi

    # By default we expect to shift one argument at a time
    shift_count=1
    if [ "${#param_pair[@]}" -gt "1" ] ; then
      # This is a param with equals notation
      value="${param_pair[1]}"
    else
      # This is either a boolean param and there is no value,
      # or the value is the next command line argument
      # Assume the value is a boolean true, unless the next argument is found to be a value.
      value=true
      if [[ $# -gt 1 && -n "$2" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "$2" ]; then
          # The next argument has NO preceding dash so it is a value
          value="$2"
          shift_count=2
        fi
      fi
    fi

    # Check that the param being passed is one of the allowed params
    if _param_variant "$allowed_params" "$key" ; then
      # --key-name will now become param_key_name
      eval param_${key//-/_}="$value"
    else
      printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
    fi
    shift $shift_count
  done
}

Dan gunakan seperti ini:

# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85

# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

# Get the params from arguments provided
_get_params $*
Heath Dutton
sumber
1

Agar tetap cross-platform yang kompatibel, dan menghindari ketergantungan pada executable eksternal, saya porting beberapa kode dari bahasa lain.

Saya merasa sangat mudah digunakan, berikut adalah contohnya:

ArgParser::addArg "[h]elp"    false    "This list"
ArgParser::addArg "[q]uiet"   false    "Supress output"
ArgParser::addArg "[s]leep"   1        "Seconds to sleep"
ArgParser::addArg "v"         1        "Verbose mode"

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

ArgParser::isset "quiet" \
   && echo "Quiet!" \
   || echo "Noisy!"

local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
   && echo "Sleep for $__sleep seconds" \
   || echo "No value passed for sleep"

# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"

BASH yang diperlukan sedikit lebih lama dari yang seharusnya, tetapi saya ingin menghindari ketergantungan pada array asosiatif BASH 4. Anda juga dapat mengunduh ini langsung dari http://nt4.com/bash/argparser.inc.sh

#!/usr/bin/env bash

# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh

# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh

unset EXPLODED
declare -a EXPLODED
function explode 
{
    local c=$# 
    (( c < 2 )) && 
    {
        echo function "$0" is missing parameters 
        return 1
    }

    local delimiter="$1"
    local string="$2"
    local limit=${3-99}

    local tmp_delim=$'\x07'
    local delin=${string//$delimiter/$tmp_delim}
    local oldifs="$IFS"

    IFS="$tmp_delim"
    EXPLODED=($delin)
    IFS="$oldifs"
}


# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "$1" && upvar $1 "value(s)"
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
        fi
    fi
}

function decho
{
    :
}

function ArgParser::check
{
    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        matched=0
        explode "|" "${__argparser__arglist[$i]}"
        if [ "${#1}" -eq 1 ]
        then
            if [ "${1}" == "${EXPLODED[0]}" ]
            then
                decho "Matched $1 with ${EXPLODED[0]}"
                matched=1

                break
            fi
        else
            if [ "${1}" == "${EXPLODED[1]}" ]
            then
                decho "Matched $1 with ${EXPLODED[1]}"
                matched=1

                break
            fi
        fi
    done
    (( matched == 0 )) && return 2
    # decho "Key $key has default argument of ${EXPLODED[3]}"
    if [ "${EXPLODED[3]}" == "false" ]
    then
        return 0
    else
        return 1
    fi
}

function ArgParser::set
{
    key=$3
    value="${1:-true}"
    declare -g __argpassed__$key="$value"
}

function ArgParser::parse
{

    unset __argparser__argv
    __argparser__argv=()
    # echo parsing: "$@"

    while [ -n "$1" ]
    do
        # echo "Processing $1"
        if [ "${1:0:2}" == '--' ]
        then
            key=${1:2}
            value=$2
        elif [ "${1:0:1}" == '-' ]
        then
            key=${1:1}               # Strip off leading -
            value=$2
        else
            decho "Not argument or option: '$1'" >& 2
            __argparser__argv+=( "$1" )
            shift
            continue
        fi
        # parameter=${tmp%%=*}     # Extract name.
        # value=${tmp##*=}         # Extract value.
        decho "Key: '$key', value: '$value'"
        # eval $parameter=$value
        ArgParser::check $key
        el=$?
        # echo "Check returned $el for $key"
        [ $el -eq  2 ] && decho "No match for option '$1'" >&2 # && __argparser__argv+=( "$1" )
        [ $el -eq  0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true "${EXPLODED[@]}"
        [ $el -eq  1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of '$2'"   >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
        shift
    done
}

function ArgParser::isset
{
    declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0
    return 1
}

function ArgParser::getArg
{
    # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
    varname="__argpassed__$1"
    echo "${!varname}"
}

##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
    local __varname="__argpassed__$1"
    local __value="${!__varname}"
    test -z "$__value" && return 1
    local "$3" && upvar $3 "$__value"
    return 0
}

function ArgParser::__construct
{
    unset __argparser__arglist
    # declare -a __argparser__arglist
}

##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
    # check for short arg within long arg
    if [[ "$1" =~ \[(.)\] ]]
    then
        short=${BASH_REMATCH[1]}
        long=${1/\[$short\]/$short}
    else
        long=$1
    fi
    if [ "${#long}" -eq 1 ]
    then
        short=$long
        long=''
    fi
    decho short: "$short"
    decho long: "$long"
    __argparser__arglist+=("$short|$long|$1|$2|$3")
}

## 
# @brief show available command line arguments
##
function ArgParser::showArgs
{
    # declare -p | grep argparser
    printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
    printf "Defaults for the options are specified in brackets.\n\n";

    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        local shortname=
        local fullname=
        local default=
        local description=
        local comma=

        explode "|" "${__argparser__arglist[$i]}"

        shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: 
        fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
        test -n "$shortname" \
            && test -n "$fullname" \
            && comma=","

        default="${EXPLODED[3]}"
        case $default in
            false )
                default=
                ;;
            "" )
                default=
                ;;
            * )
                default="[$default]"
        esac

        description="${EXPLODED[4]}"

        printf "  %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
    done
}

function ArgParser::test
{
    # Arguments with a default of 'false' do not take paramaters (note: default
    # values are not applied in this release)

    ArgParser::addArg "[h]elp"      false       "This list"
    ArgParser::addArg "[q]uiet" false       "Supress output"
    ArgParser::addArg "[s]leep" 1           "Seconds to sleep"
    ArgParser::addArg "v"           1           "Verbose mode"

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset "quiet" \
        && echo "Quiet!" \
        || echo "Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
        && echo "Sleep for $__sleep seconds" \
        || echo "No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo "Sleep set to: $( ArgParser::getArg sleep )"

    echo "Remaining command line: ${__argparser__argv[@]}"

}

if [ "$( basename "$0" )" == "argparser.inc.sh" ]
then
    ArgParser::test "$@"
fi
Orwellophile
sumber
1

Jika semua opsi panjang Anda memiliki karakter pertama yang unik dan cocok, sebagai opsi pendek, maka misalnya

./slamm --chaos 23 --plenty test -quiet

Sama dengan

./slamm -c 23 -p test -q

Anda dapat menggunakan ini sebelum getopts untuk menulis ulang $ args:

# change long options to short options

for arg; do 
    [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
    if [ "${arg:0:2}" == "--" ]; 
       then args="${args} -${arg:2:1}" 
       else args="${args} ${delim}${arg}${delim}"
    fi
done

# reset the incoming args
eval set -- $args

# proceed as usual
while getopts ":b:la:h" OPTION; do
    .....

Terima kasih atas mtvee untuk inspirasi ;-)

Commonpike
sumber
Saya tidak mendapatkan arti penting dari eval di sini
user.friendly
1

jika ini adalah bagaimana Anda ingin memanggil skrip

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"

maka Anda dapat mengikuti cara paling sederhana untuk mencapainya dengan bantuan getopt dan --longtions

coba ini, semoga ini bermanfaat

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
)


echo $opts

eval set --$opts

while true; do
    case "$1" in
    --input1)  
        shift
        empId=$1
        ;;
    --input2)  
        shift
        fromDate=$1
        ;;
    --input3)  
        shift
        toDate=$1
        ;;
      --)
        shift
        break
        ;;
    esac
    shift
done
Ashish Shetkar
sumber
0

getopts "bisa digunakan" untuk mem-parsing opsi-opsi panjang selama Anda tidak berharap mereka memiliki argumen ...

Begini caranya:

$ cat > longopt
while getopts 'e:-:' OPT; do
  case $OPT in
    e) echo echo: $OPTARG;;
    -) #long option
       case $OPTARG in
         long-option) echo long option;;
         *) echo long option: $OPTARG;;
       esac;;
  esac
done

$ bash longopt -e asd --long-option --long1 --long2 -e test
echo: asd
long option
long option: long1
long option: long2
echo: test

Jika Anda mencoba menggunakan OPTIND untuk mendapatkan parameter untuk opsi panjang, getopt akan memperlakukannya sebagai yang pertama tanpa parameter posisi opsional dan akan berhenti mem-parsing parameter lainnya. Dalam kasus seperti itu, Anda akan lebih baik menanganinya secara manual dengan pernyataan kasus sederhana.

Ini akan "selalu" berfungsi:

$ cat >longopt2
while (($#)); do
    OPT=$1
    shift
    case $OPT in
        --*) case ${OPT:2} in
            long1) echo long1 option;;
            complex) echo comples with argument $1; shift;;
        esac;;

        -*) case ${OPT:1} in
            a) echo short option a;;
            b) echo short option b with parameter $1; shift;;
        esac;;
    esac
done


$ bash longopt2 --complex abc -a --long -b test
comples with argument abc
short option a
short option b with parameter test

Meskipun tidak sefleksiet getopts dan Anda harus melakukan sendiri sebagian besar kesalahan pengecekan kode dalam kasus kasing ...

Tapi itu pilihan.

estani
sumber
Tetapi opsi panjang sering berharap argumen. Dan Anda bisa melakukan lebih banyak dengan - untuk membuatnya bekerja meskipun itu sesuatu yang meretas. Pada akhirnya orang dapat berargumen bahwa jika itu tidak mendukungnya secara alami maka setiap cara untuk mengimplementasikannya adalah sesuatu peretasan, namun demikian Anda dapat memperpanjang - juga. Dan ya shift sangat berguna tetapi tentu saja jika ia mengharapkan argumen itu bisa berakhir bahwa argumen berikutnya (jika tidak ditentukan oleh pengguna) adalah bagian dari argumen yang diharapkan.
Pryftan
ya, ini adalah poc untuk nama argumen panjang tanpa argumen, untuk membedakan antara Anda memerlukan beberapa jenis konfigurasi, seperti getops. Dan mengenai shift, Anda selalu dapat "mengembalikannya" dengan set. Dalam hal apa pun itu harus dapat dikonfigurasi jika suatu parameter diharapkan atau tidak. Anda bahkan dapat menggunakan beberapa sihir untuk itu, tetapi kemudian Anda akan memaksa pengguna untuk menggunakan - untuk memberi sinyal bahwa sihir berhenti dan parameter posisi mulai, yang lebih buruk lagi.
estani
Cukup adil. Itu lebih masuk akal. Aku bahkan tidak ingat apa yang sedang terjadi dan aku benar-benar lupa tentang pertanyaan ini. Saya hanya samar-samar ingat bagaimana saya menemukannya bahkan. Bersulang. Oh dan dapatkan +1 untuk idenya. Anda melalui upaya dan Anda juga menjelaskan apa yang Anda maksudkan. Saya menghormati orang-orang yang melakukan upaya untuk memberikan ide kepada orang lain, dll.
Pryftan
0

Builtingetopts hanya mengurai opsi pendek (kecuali dalam ksh93), tetapi Anda masih dapat menambahkan beberapa baris skrip untuk membuat getop menangani opsi panjang.

Berikut adalah bagian dari kode yang ditemukan di http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

  #== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
  #== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
    [foo]=f
    [bar]=b
    [foobar]=F
    [barfoo]=B
    [help]=h
    [man]=h
)

  #== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
    #== translate long options to short ==#
    if [[ "x$OPTION" == "x-" ]]; then
        LONG_OPTION=$OPTARG
        LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
        LONG_OPTIND=-1
        [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
        [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
        OPTION=${ARRAY_OPTS[$LONG_OPTION]}
        [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

        if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
            if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                OPTION=":" OPTARG="-$LONG_OPTION"
            else
                OPTARG="$LONG_OPTARG";
                if [[ $LONG_OPTIND -ne -1 ]]; then
                    [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                    shift $OPTIND
                    OPTIND=1
                fi
            fi
        fi
    fi

    #== options follow by another option instead of argument ==#
    if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
        OPTARG="$OPTION" OPTION=":"
    fi

    #== manage options ==#
    case "$OPTION" in
        f  ) foo=1 bar=0                    ;;
        b  ) foo=0 bar=1                    ;;
        B  ) barfoo=${OPTARG}               ;;
        F  ) foobar=1 && foobar_name=${OPTARG} ;;
        h ) usagefull && exit 0 ;;
        : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
        ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
    esac
done
shift $((${OPTIND} - 1))

Ini tesnya:

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

Kalau tidak, di Korn Shell ksh93 baru-baru ini, getoptssecara alami dapat mengurai opsi panjang dan bahkan menampilkan halaman manual. (Lihat http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )

Michel VONGVILAY uxora.com
sumber
0

Th built-in OS X (BSD) getopt tidak mendukung opsi panjang, tapi versi GNU tidak: brew install gnu-getopt. Kemudian, sesuatu yang mirip dengan: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.

wprl
sumber
0

EasyOptions menangani opsi pendek dan panjang:

## Options:
##   --verbose, -v   Verbose mode
##   --logfile=NAME  Log filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "log file: ${logfile}"
    echo "arguments: ${arguments[@]}"
fi
Renato Silva
sumber