Uzun ve kısa komut satırı seçeneklerini işlemek için getopts kullanma


410

Kabuk betiğimi kullanarak komut satırı seçeneklerinin uzun ve kısa biçimlerini çağırmak istiyorum.

Bunun kullanılabileceğini biliyorum getopts, ama Perl'de olduğu gibi, aynı şeyi kabukla yapamadım.

Bunun nasıl yapılabileceğine dair herhangi bir fikir, böylece aşağıdaki gibi seçenekleri kullanabilirim:

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

Yukarıda, her iki komut da kabuğum için aynı anlama geliyor, ancak bunları kullanarak getoptsbunları uygulayamadım mı?


2
IMHO, kabul edilen cevap en iyisi değil. @Arvid Requate'in gösterdiği gibi, yapılabilecek hem "-" hem de "-" bağımsız değişkenlerini işlemek için getopts nasıl kullanılacağını göstermez. Ben benzer bir kavram kullanarak başka bir cevap eklemek, ama aynı zamanda "gerekli" argümanlar için değer eklemek için "unutma" kullanıcı hatası ile ilgilenir. Anahtar nokta: getopts çalışmak için yapılabilir. Platformlar arası taşınabilirlik gerekiyorsa kullanıcı bunun yerine "getopt" kullanmaktan kaçınmalıdır. Ayrıca, getopts kabuklar için POSIX standardının bir parçasıdır, bu nedenle taşınabilir olması muhtemeldir.
pauljohn32

Yanıtlar:


304

Dikkate alınabilecek üç uygulama vardır:

  • Bash yerleşik getopts. Bu, çift tire önekine sahip uzun seçenek adlarını desteklemez. Yalnızca tek karakterli seçenekleri destekler.

  • Bağımsız getoptkomutun BSD UNIX uygulaması ( MacOS'un kullandığı budur). Bu, uzun seçenekleri de desteklemez.

  • Bağımsız GNU uygulaması getopt. GNU getopt(3)( getopt(1)Linux'ta komut satırı tarafından kullanılır ) uzun seçeneklerin ayrıştırılmasını destekler.


Diğer bazı cevaplar, getoptsuzun seçenekleri taklit etmek için bash yerleşimini kullanmak için bir çözüm göstermektedir . Bu çözüm aslında karakteri "-" olan kısa bir seçenek yapar. Yani bayrak olarak "-" alırsınız. Daha sonra bunu takip eden her şey OPTARG olur ve OPTARG'ı iç içe geçmiş bir şekilde test edersinizcase .

Bu zekidir, ancak uyarılarla birlikte gelir:

  • getoptsopt spec zorlayamaz. Kullanıcı geçersiz bir seçenek sunarsa hata döndüremez. OPTARG'ı ayrıştırırken kendi hata denetiminizi yapmanız gerekir.
  • OPTARG, uzun seçeneğinizin bir argümanı olduğunda kullanımı zorlaştıran uzun seçenek adı için kullanılır. Sonunda kendinizi ek bir durum olarak kodlamak zorunda kalırsınız.

Bu nedenle, uzun seçenekler için destek eksikliği etrafında çalışmak için daha fazla kod yazmak mümkün olsa da, bu çok daha fazla iştir ve kodunuzu basitleştirmek için bir getopt ayrıştırıcı kullanma amacını kısmen yener.


18
Yani. Platformlar arası, taşınabilir çözüm nedir?
troelskn

6
GNU Getopt tek seçenek gibi görünüyor. Mac'te macports'tan GNU getopt uygulamasını yükleyin. Windows'ta Cygwin ile GNU getopt kuracaktım.
Bill Karwin

2
Anlaşılan , Ksh getopts olabilir uzun seçenekleri işlemek.
Tgr

1
@Bill 1, aynı zamanda kaynak (gelen yapı getopt oldukça basit olmasına rağmen software.frodo.looijaard.name/getopt Mac'te). Ayrıca sisteminizde kurulu olan getopt sürümünü "getopt -T; echo $?" Komut dosyalarından da kontrol edebilirsiniz.
Chinasaur

8
@Bill Karwin: "Yerleşik bash getopts, çift tire önekiyle uzun seçenek adlarını desteklemiyor." Ancak getopts uzun seçenekleri desteklemek için yapılabilir: aşağıdaki stackoverflow.com/a/7680682/915044 adresine bakın.
TomRoche

305

getoptve getoptsfarklı yaratıklardır ve insanların yaptıklarıyla ilgili yanlış anlaşılmaları var gibi görünüyor. getoptsBir komuta dahili olan bashbir döngüde işlem komut satırı seçeneklerine ve daha fazla onları işleyebilir Yerleşik değişkenler, böylece için sırayla her bulunan seçenek ve değer atama. getoptBununla birlikte, harici bir yardımcı programdır ve seçeneklerinizi sizin için bash getopts, Perl Getoptmodülü veya Python optparse/ argparsemodülleri gibi işlemez . Tek getoptyapmanız gereken, geçirilen seçenekleri kanonik hale getirmek - yani daha standart bir forma dönüştürmek, böylece bir kabuk betiğinin bunları işlemesini kolaylaştırmaktır. Örneğin, bir uygulaması getoptaşağıdakileri dönüştürebilir:

myscript -ab infile.txt -ooutfile.txt

bunun içine:

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

Gerçek işlemi kendiniz yapmanız gerekir. getoptSeçenekleri belirleme yolunda çeşitli kısıtlamalar yaparsanız hiç kullanmanız gerekmez :

  • argüman başına yalnızca bir seçenek koyun;
  • tüm seçenekler herhangi bir konum parametresinden önce gelir (örn. seçenek olmayan argümanlar);
  • değer içeren seçenekler için (örn. -oyukarıda), değerin ayrı bir bağımsız değişken olarak (boşluktan sonra) gitmesi gerekir.

Neden kullanmak getoptyerine getopts? Bunun temel nedeni, yalnızca GNU'nun getoptuzun adlı komut satırı seçenekleri için destek vermesidir. 1 (Linux'ta GNU getoptvarsayılan değerdir. Mac OS X ve FreeBSD temel ve çok kullanışlı değildir getopt, ancak GNU sürümü yüklenebilir; aşağıya bakın.)

Örneğin, işte şöyle getoptbir senaryomdan GNU kullanımına bir örnek 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

Bu, --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"benzer veya benzer seçenekleri belirlemenizi sağlar . Çağrının etkisi getopt, seçenekleri --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"daha kolay işleyebilmeniz için seçenekleri standartlaştırmaktır . Etrafta alıntılar yapmak "$1"ve "$2"içinde boşluk olan argümanların düzgün bir şekilde ele alınmasını sağlamak önemlidir.

İlk 9 satırı silerseniz ( eval setsatır boyunca her şey ), kod yine de çalışır ! Ancak, kodunuz ne tür seçenekleri kabul ettiğine göre çok daha pickier olacaktır: Özellikle, yukarıda açıklanan "standart" formdaki tüm seçenekleri belirtmeniz gerekir. Kullanımı ile getopt, ancak, grup tek harfli seçenekleri, uzun seçenekler, kullanım ya kısa olmayan belirsiz formlarını kullanabilirsiniz --file foo.txtveya --file=foo.txtiki stil, kullanımı -m 4096veya -m4096stil, herhangi bir sırada seçeneklerini ve olmayan seçenekleri mix vb getoptayrıca tanınmayan veya belirsiz seçenekler bulunursa bir hata mesajı verir.

NOT : Aslında temel ve GNU olmak üzere tamamen farklı iki sürümü vardır . 2 Temel oldukça bozuk: Sadece uzun seçenekleri işlemekle kalmaz, aynı zamanda argümanların veya boş argümanların içindeki gömülü boşlukları bile işleyemez, oysa bunu doğru yapar. Yukarıdaki kod temel olarak çalışmaz . GNU Linux'a varsayılan olarak yüklenir, ancak Mac OS X ve FreeBSD'de ayrı olarak yüklenmesi gerekir. Mac OS X'te MacPorts'u ( http://www.macports.org ) yükleyin ve GNU'yu (genellikle içine ) yüklemek için yapın ve bunun önündeki kabuk yolunuzda olduğundan emin olungetoptgetoptgetoptgetoptgetoptsgetoptgetoptsudo port install getoptgetopt/opt/local/bin/opt/local/bin/usr/bin . FreeBSD'de yükleyinmisc/getopt.

Kendi programınız için örnek kodu değiştirmek için hızlı bir kılavuz: İlk birkaç satırda, çağıran hat hariç hepsi aynı kalması gereken "kaynak plakası" dır getopt. Program adını sonra değiştirmeli, sonra -nkısa seçenekler -ove sonra uzun seçenekler belirtmelisiniz --long. Değer alan seçeneklerden sonra iki nokta üst üste koyun.

Son olarak, setbunun yerine kodu görürseniz eval set, BSD için yazılmıştır getopt. Düzenin GNU ile düzgün çalışmadığı halde, eval sether iki sürümüyle de iyi çalışan stili kullanmak için değiştirmelisiniz .getoptsetgetopt

1 Aslında getoptsiçinde ksh93desteklerin seçenekleri uzun adlandırılmış ama bu kabuk sık olarak kullanılmaz bash. İçinde zsh, zparseoptsbu işlevi almak için kullanın .

2 Teknik olarak "GNU getopt" yanlış adlandırmadır; bu sürüm aslında GNU projesi yerine Linux için yazılmıştır. Bununla birlikte, tüm GNU kurallarını takip eder ve "GNU getopt" terimi yaygın olarak kullanılır (örneğin, FreeBSD'de).


3
Bu çok yardımcı oldu, seçenekleri kontrol etmek ve daha sonra bu seçenekleri çok basit bir döngüde işlemek için kullanma fikri, bir bash betiğine uzun stil seçenekleri eklemek istediğimde gerçekten iyi çalıştı. Teşekkürler.
ianmjones

2
getoptLinux'ta bir GNU yardımcı programı değildir ve geleneksel olarak getoptbaşlangıçta BSD'den değil, AT&T Unix'ten gelir. ksh93'ler getopts(AT&T'den de) GNU tarzı uzun seçenekleri destekler.
Stephane Chazelas

@StephaneChazelas - yorumlarınızı yansıtacak şekilde düzenlendi. "GNU getopt" terimini bir yanlış ad olsa da tercih ediyorum, çünkü bu sürüm GNU sözleşmelerini izliyor ve genellikle bir GNU programı gibi davranıyor (örneğin kullanmak POSIXLY_CORRECT), "Linux gelişmiş getopt" yanlış ise bu sürümün sadece Linux.
Urban Vagabond

1
Util-linux paketinden geliyor, bu yüzden sadece yazılım paketi sadece Linux için tasarlandığı için ( getoptdiğer Unices'e kolayca taşınabilir, ancak diğer birçok yazılım util-linuxLinux'a özgüdür). GNU getopt (3) kullanan tüm GNU olmayan programları kavrar $POSIX_CORRECT. Örneğin, bunun aplaysadece bu temelde GNU olduğunu söyleyemezsiniz . FreeBSD'nin GNU getopt'tan bahsettiğinde, bunların GNU getopt (3) C API'si anlamına geldiğinden şüpheleniyorum.
Stephane Chazelas

@StephaneChazelas - FreeBSD'nin "İnşa bağımlılığı: Lütfen GNU getopt'i yükleyin" şeklinde bir hata mesajı var getopt.
Urban Vagabond

202

Bash yerleşik getopts işlevi, optspec'e bir tire karakteri ve ardından iki nokta üst üste işareti koyarak uzun seçenekleri ayrıştırmak için kullanılabilir:

#!/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

= Yürütülebilir dosya adına kopyaladıktan sonra getopts_test.shiçinde geçerli çalışma dizininin , tek gibi çıktı üretebilir

$ ./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'

Açıkçası getopts OPTERRuzun seçenekler için ne denetim ne de seçenek-argüman ayrıştırma gerçekleştirmez . Yukarıdaki kod parçası bunun manuel olarak nasıl yapılabileceğini göstermektedir. Temel ilke Debian Almquist kabuğunda da ("çizgi") çalışır. Özel durumu not edin:

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

Http://mywiki.wooledge.org/BashFAQ adresindeki GreyCat'in dikkat çektiği gibi , bu hile, seçenek argümanına izin veren kabuğun standart olmayan bir davranışından yararlanır (yani, "-f dosya adındaki" dosya adı) seçeneğe birleştirilecek ("-filename" de olduğu gibi). POSIX "- longoption" seçeneğini-ayrıştırma sonlandırmak istiyorum ve seçenek olmayan argümanlar haline bütün longoptions çevirmek standart durumunda aralarında bir boşluk olması gerektiğini söyledi.


2
Bir soru: !içinde semantik val="${!OPTIND}nedir?
TomRoche

2
@TomRoche Bu Dolaylı Yerine Koyma: unix.stackexchange.com/a/41293/84316
ecbrodie

2
@ecbrodie: Bunun nedeni, sadece bir argümanın aksine iki argümanın işlenmiş olmasıdır. İlk argüman "loglevel" kelimesi, ikinci argüman ise bu argümanın argümanıdır. Bu arada, getoptsotomatik olarak sadece OPTIND1 ile artar , ancak bizim durumumuzda bunu 2 oranında artırmamız gerekir, bu yüzden manuel olarak 1 arttırırız, sonra getoptsotomatik olarak bizim için 1 artar.
Victor Zamanian

3
Burada bash dengesine girdiğimizden: Çıplak değişken isimlerine aritmetik ifadelere izin verilir, $gerekli değildir. OPTIND=$(( $OPTIND + 1 ))sadece olabilir OPTIND=$(( OPTIND + 1 )). Daha da ilginç olanı, bir aritmetik ifadenin içindeki değişkenleri atayabilir ve artırabilirsiniz, bu yüzden onu daha da kısaltmak : $(( ++OPTIND ))veya her zaman pozitif olacak (( ++OPTIND ))hesaba katmak mümkündür ++OPTIND, bu nedenle -eseçenekle bir kabuk çalışmasını tetiklemez . :-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
clacke

3
Neden --very-baduyarı vermiyor?
Tom Hale

148

Yerleşik getoptskomut tek harf seçenekleri sadece sınırlı hâlâ AFAIK olduğunu.

getoptAyrıştırmak daha kolay olacak şekilde bir dizi seçeneği yeniden düzenleyecek harici bir program vardır (ya da eskiden) . Bu tasarımı uzun seçenekleri de kapsayacak şekilde uyarlayabilirsiniz. Örnek kullanım:

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
...

Komutla benzer bir şema kullanabilirsiniz getoptlong.

Dış getoptprogramdaki temel zayıflığın, içindeki boşluklarla argümanları ele alma ve bu alanları doğru bir şekilde koruma zorluğu olduğuna dikkat edin. Bu nedenle, yerleşik olan getopts, yalnızca tek harfli seçenekleri işleyebilmesine rağmen, üstündür.


11
getopt, (farklı bir çağrı kuralı olan) GNU sürümü hariç, temelde kırılmıştır. Bunu kullanma. Lütfen ** getopts yerine bash-hackers.org/wiki/doku.php/howto/getopts_tutorial
hendry

9
@hendry - kendi bağlantınızdan: "Getopts'un GNU tarzı uzun seçenekleri (- myoption) veya XF86 tarzı uzun seçenekleri (- myoption) ayrıştıramadığını unutmayın!"
Tom Auger

1
Jonathan - eval setGNU getopt (Linux'ta varsayılan) ile de düzgün çalışması ve boşlukları doğru işlemesi için örneği tırnaklarla kullanmak üzere yeniden yazmalısınız (aşağıdaki cevabım).
Urban Vagabond

@UrbanVagabond: Bunu neden yapmam gerektiğinden emin değilim. Soru Linux değil Unix olarak etiketlendi. Geleneksel mekanizmayı kasıtlı olarak gösteriyorum ve argümanlardaki boşluklarla ilgili sorunlar var, vb. İsterseniz modern Linux'a özgü sürümü gösterebilirsiniz ve cevabınız bunu yapar. (Unutmayın, passim, kullanımınızın ${1+"$@"}tuhaf ve modern kabuklarda ve özellikle Linux'ta bulabileceğiniz herhangi bir kabukta gerekli olanlarla çelişiyor. Bkz . / Bin / sh için / 1 $ bu notasyonun tartışılması.)
Jonathan Leffler

Bunu yapmalısınız, çünkü eval sethem GNU hem de BSD ile doğru olanı yapar getopt, oysa sade setsadece BSD ile doğru olanı yapar getopt. Yani eval set, insanları bunu yapma alışkanlığına girmeye teşvik etmek için de kullanabilirsiniz . BTW teşekkürler, bunun ${1+"$@"}artık gerekli olmadığını fark etmedim . Hem Mac OS X hem de Linux üzerinde çalışan şeyler yazmam gerekiyor - ikisi arasında çok fazla taşınabilirliği zorluyorlar. Sadece kontrol ve "$@"aslında tümü üzerinde doğru olanı yapar sh, bash, kshve zshMac OS X altında; kesinlikle Linux altında da.
Urban Vagabond

78

İşte aslında uzun seçeneklerle getopt kullanan bir örnek:

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

1
eval setGNU getopt (Linux'ta varsayılan) ile de düzgün çalışması ve boşlukları doğru işlemesi için örneği tırnaklarla kullanmak üzere yeniden yazmalısınız (aşağıdaki cevabım).
Urban Vagabond

2
Bu getoptsoru hakkında getoptsolsa da kullanıyor.
Niklas Berglund

1
Are (--, (-*ve (*geçerli desenleri? Nasıl onlar farklı --, -*ve *?
Maëlan

1
Maëlan @ - gelen parantez açılmasından çok, isteğe bağlı (--)özdeş --)bir de casedörtlük. Opsiyonel önde gelen parenlerin eşit olmayan girintisini ve tutarsız kullanımını görmek garip, ancak cevaplayıcının mevcut kodu benim için geçerli görünüyor.
Adam Katz

59

Uzun seçenekler standart getoptsyerleşik tarafından -“seçenek” e “argümanlar” olarak ayrıştırılabilir

Bu taşınabilir ve yerel POSIX kabuğudur - harici programlar veya bashisms gerekmez.

Argümanları olarak Bu kılavuz uygular uzun seçenekleri -seçeneği, yani --alphatarafından görülür getoptsşekilde -argümanla alphave --bravo=fooolarak görülüyor -argüman ile bravo=foo. Gerçek argüman basit değiştirme ile hasat edilebilir: ${OPTARG#*=}.

Bu örnekte -bve -c(ve uzun biçimleri --bravove --charlie) zorunlu argümanlara sahiptir. Uzun seçeneklere yönelik argümanlar eşit işaretlerden sonra gelir, örneğin --bravo=foo(uzun seçenekler için alan sınırlayıcıların uygulanması zor olacaktır, aşağıya bakın).

Bu getoptsyerleşikcmd --bravo=foo -ac FILE olanı kullandığı için , bu çözüm (birleşik seçenekleri olan -ave -cuzun seçenekleri standart seçeneklerle harmanlayan) gibi kullanımı desteklerken, buradaki diğer yanıtların çoğu ya mücadele eder ya da başarısız olur.

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

Seçenek bir tire ( -) olduğunda, uzun bir seçenektir. getoptsgerçek uzun seçeneği $OPTARG, örneğin --bravo=foobaşlangıçta OPT='-'ve OPTARG='bravo=foo'. ifDörtlük setleri $OPTiçeriğine $OPTARGilk eşittir işaretinden önce ( bravodaha sonra bizim örneğimizde) ve başından itibaren bu kaldırır $OPTARG(getirili =foohiçbir varsa bu adımda veya boş bir dizede =). Sonunda, tartışmanın öncülüğünü kaldırıyoruz =. Bu noktada, $OPTkısa bir seçenek (bir karakter) veya uzun bir seçenek (2+ karakter).

Ardından casekısa veya uzun seçeneklerle eşleşir. Kısa seçenekler için getoptsotomatik olarak seçenekler ve eksik argümanlar hakkında şikayette bulunur, bu yüzden boşken needs_argölümcül olarak çıkan işlevi kullanarak bunları manuel olarak çoğaltmamız gerekir $OPTARG. ??*Koşul kalan uzun seçeneği (maç olacak ?bir tek karakterle eşleşir ve *maçları sıfır veya daha fazla, bu yüzden ??*bize çıkmadan önce "Yasadışı seçenek" hatasını çıkarmaya izin 2+ karakterle eşleşir).

(Tüm büyük harf değişken adları hakkında bir not: Genellikle, tüm büyük değişkenleri sistem kullanımı için ayırmak için tavsiye edilir. Ben uyumlu tutmak $OPTiçin tüm büyük harf olarak saklıyorum $OPTARG, ama bu bu kuralı bozar. çünkü bu sistemin yapması gereken bir şeydir ve güvenli olmalıdır çünkü böyle bir değişkeni kullanan standartlar (afaik) yoktur.)


Uzun seçeneklere yönelik beklenmedik bağımsız değişkenlerden şikayet etmek için zorunlu bağımsız değişkenler için ne yaptığımızı taklit edin: bir yardımcı işlev kullanın. Beklenmedik bir argümandan şikayet etmek için testi ters çevirin:

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

Bir bu cevabın eski sürümü boşlukla sınırlandırılmış argümanlarla uzun seçenekler kabul girişimini vardı, ama güvenilir değildi; getoptsargümanın kapsamının ötesinde olduğu ve manuel olarak artırmanın $OPTINDtüm kabuklarda çalışmadığı varsayımından erken feshedilebilir .

Bu, aşağıdaki tekniklerden biri kullanılarak gerçekleştirilebilir:

ve sonra böyle bir şeyle [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))


Çok güzel kendi kendine yeten çözüm. Bir soru: letter-cTartışmaya ihtiyaç duyulmadığından, kullanmak yeterli olmaz letter-c)mı? *gereksiz görünüyor.
Philip Kearns

1
@Arne Konumsal argümanlar kötü UX; anlaşılması zor ve isteğe bağlı argümanlar oldukça dağınık. getoptsilk konum argümanında durur, çünkü onlarla başa çıkmak için tasarlanmamıştır. Bu örneğin kendi savları tamamen alt komutlar veriyor git diff --colorben yorumlamak istiyorum, böylece command --foo=moo bar --baz wazsahip olarak --foobir argüman olarak commandve --baz wazhiç (opsiyonlu) bağımsız değişken olarak baralt komuta. Bu, yukarıdaki kod ile yapılabilir. Reddediyorum --bravo -blahçünkü --bravobir tartışma gerektiriyor ve -blahbaşka bir seçenek olmadığı belli değil.
Adam Katz

1
UX hakkında aynı fikirde değilim: konum bağımsız değişkenleri, sayılarını (en fazla 2 veya 1 artı aynı türden N ile) sınırladığınız sürece yararlı ve kolaydır. Kullanıcılar anahtar kelime argümanları ile serpiştirilebilmelidir, çünkü kullanıcılar daha sonra adım adım bir komut oluşturabilir (yani ls abc -la).
Arne Babenhauserheide

1
@AdamKatz: Bununla küçük bir makale yazdım: draketo.de/english/free-software/shell-argument-parsing - sondaki seçenekleri yakalamak için kalan argümanların tekrar tekrar okunmasını içerir.
Arne Babenhauserheide

1
@ArneBabenhauserheide: Bu yanıtı, alan sınırlamalı argümanları desteklemek için güncelledim. evalPOSIX kabuğunda gerektirdiğinden , cevabın geri kalanının altında listelenir.
Adam Katz

33

Taşınabilir bir kabuk kütüphanesi olan shFlags'a bir göz atın (yani: sh, bash, dash, ksh, Linux'ta zsh, Solaris, vb.).

Komut satırınıza bir satır eklemek kadar yeni bayrak eklemeyi kolaylaştırır ve otomatik olarak oluşturulan bir kullanım işlevi sağlar.

İşte shFlagHello, world! kullanarak basit :

#!/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}!"

Uzun seçenekleri (örneğin Linux) destekleyen gelişmiş getopt'e sahip işletim sistemleri için şunları yapabilirsiniz:

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

Geri kalanı için kısa seçeneği kullanmalısınız:

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

Yeni bir bayrak eklemek, yeni bir bayrak eklemek kadar basittir DEFINE_ call.


2
Bu harika ama ne yazık ki benim getopt (OS X) argümanlarda boşlukları desteklemiyor: / acaba başka bir alternatif olup olmadığını merak ediyorum.
Alastair Stuart

@AlastairStuart - gerçekten OS X'de bir alternatif var. GNU getopt'i yüklemek için MacPorts kullanın (genellikle / opt / local / bin / getopt içine yüklenir).
Urban Vagabond

3
@UrbanVagabond - sistem dışı varsayılan araçların yüklenmesi maalesef yeterince taşınabilir bir araç için kabul edilebilir bir gereklilik değildir.
Alastair Stuart

@AlastairStuart - GNU getopt yerine yerleşik getopts kullanan taşınabilir bir çözüm için cevabımı görün . Temel getopts kullanımı ile aynıdır, ancak uzun seçenekler için ekstra bir yineleme vardır.
Adam Katz

31

Kullanılması getoptskısa / uzun seçenekler ve argümanlarla


Tüm kombinasyonlarla çalışır, örneğin:

  • foobar -f --bar
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB --artanlar = longhorn
  • foobar -fA "kısa metin" -B --arguments = "metin longhorn"
  • bash foobar -F --barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

Bu örnek için bazı açıklamalar

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

Kullanım işlevi nasıl görünür?

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 uzun / kısa bayraklar ve uzun argümanlar

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

Çıktı

##################################################################
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"

Yukarıdakileri uyumlu bir senaryoda birleştirmek

#!/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

Bu birden fazla uzun argümanla (-) çalışmıyor mu? Benim için sadece birincisini okuyor gibi görünüyor.
Sinaesthetic

@Sinaesthetic - Evet, evaluzun seçeneklerde aralıklı argümanlar yaklaşımı ile oynuyordum ve belirli kabuklarla güvenilmez buldum (ancak bash ile çalışacağını umuyorum, bu durumda kullanmak zorunda değilsiniz eval). Uzun seçenek argümanlarının nasıl kabul edileceğine ve alanı kullanma girişimlerine ilişkin cevabımı görün =. Bu çözüm cutbirkaç kez kullanırken çözümüm harici arama yapmıyor .
Adam Katz

24

Diğer yol...

# 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

1
Bunun her $argsyeniden atamada bir alana ihtiyaç duymuyor mu? Bu bile bashisms ile yapılabilir, ancak bu kod seçenekleri ve argümanlarda boşluk kaybedecek ( $delimhile işe yarayacağını sanmıyorum ). Bunun yerine , yalnızca ilk yinelemede boşaltmaya yetecek kadar dikkatli iseniz , döngü set içinde çalışabilirsiniz for. İşte bashisms olmadan daha güvenli bir versiyon .
Adam Katz

18

Bu şekilde çözdüm:

# 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;

Aptal mıyım falan mıyım? getoptve getoptsçok kafa karıştırıcı.


1
Bu benim için işe yarıyor gibi görünüyor, bu yöntemle sorunun ne olduğunu bilmiyorum, ama basit görünüyor, bu yüzden herkesin kullanmamasının bir nedeni olmalı.
Billy Moon

1
@Billy Evet, bu basit çünkü parametrelerimi vb. Yönetmek için herhangi bir komut dosyası kullanmıyorum. Temelde argümanlar dizesini ($ @) bir diziye dönüştürüyorum ve bunun üzerinden dönüyorum. Döngüde, geçerli değer anahtar, bir sonraki değer de olacaktır. Bu kadar basit.

1
@Theodore bu size yardımcı oldu sevindim! Benim için de bir acıydı. İlgileniyorsanız, bunun bir örneğini burada görebilirsiniz: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh

2
Kesinlikle gördüğüm en kolay yol. İfade yerine i = $ (($ i + 1)) kullanmak gibi biraz değiştirdim ama konsept hava geçirmez.
Thomas Dignan

6
Hiç aptal değilsiniz, ancak bir özelliği kaçırıyor olabilirsiniz: getopt (lar) karışık seçenekleri (ör: -ltrveya -lt -ryanı sıra -l -t -r) tanıyabilir . Ayrıca, bazı hataların ele alınmasını ve seçenekler tedavi tamamlandıktan sonra tedavi edilen parametreleri kaydırmanın kolay bir yolunu sunar.
Olivier Dulac

14

getoptBağımlılığı istemiyorsanız , bunu yapabilirsiniz:

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

Tabii ki, o zaman bir çizgi ile uzun stil seçeneklerini kullanamazsınız. Kısaltılmış sürümler eklemek istiyorsanız (örneğin, --verbose yerine --verbos), bunları manuel olarak eklemeniz gerekir.

Ancak getopts, uzun seçeneklerle birlikte işlevsellik elde etmek istiyorsanız , bunu yapmanın basit bir yoludur.

Bu pasajı bir özete de koydum .


Bu bir seferde sadece uzun bir seçenekle çalışıyor gibi görünüyor, ama ihtiyacımı karşıladı. Teşekkür ederim!
kingjeffrey

Özel durumda --)bir shift ;eksik var gibi görünüyor . Şu anda --ilk seçenek olmayan argüman olarak kalacaktır.
dgw

Ben dgw işaret gibi --seçenek shiftorada bir ihtiyacı olsa da, bu aslında daha iyi bir cevap olduğunu düşünüyorum . Bunun daha iyi olduğunu söylüyorum çünkü alternatifler ya platforma bağlı sürümlerdir getoptya getopts_longda kısa seçenekleri yalnızca komutun başlangıcında kullanılmaya zorlamak zorundasınız (yani - getoptsdaha sonra uzun seçenekleri kullanıyorsunuz ); ve tam kontrol.
Haravikk

Bu cevap, işi yapmak için neden bu kadar net ve anlaşılır bir şeyden başka bir şeyle yapılabilecek bir düzinelerce cevabımız olduğunu merak ediyor. çözümden ve milyar getopt (lar) ın kullanım durumlarının kanıtlamaktan başka bir nedeni varsa kendini.
Florian Heigl

11

Yerleşik getoptsbunu yapamaz. Bunu yapabilen harici bir getopt (1) programı var, ancak bunu sadece util-linux paketinden Linux'ta alıyorsunuz . Örnek bir komut dosyası getopt-parse.bash ile birlikte gelir .

getopts_longKabuk fonksiyonu olarak yazılı da vardır .


3
getopt1993 FreeBSD sürüm 1.0 dahil edildi ve o zamandan bu yana FreeBSD parçası olmuştur. Bu nedenle, Apple'ın Darwin projesine dahil edilmek üzere FreeBSD 4.x'ten kabul edildi. OS X 10.6.8'den itibaren Apple'ın içerdiği kılavuz sayfası, FreeBSD kılavuz sayfasının tam bir kopyası olmaya devam ediyor. Evet, OS X'te ve Linux'un yanı sıra diğer işletim sistemlerinde de var. Yanlış bilgi için bu cevaba -1.
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

2
Bir açıklama iyi olurdu. İlk komut dosyası yalnızca kısa seçenek kabul ederken, ikinci komut dosyasının uzun seçenek bağımsız değişken ayrıştırmasında bir hata vardır; "${1:0:1}"değişkeni # 1 argümanı, dizin 0, uzunluk 1'deki alt dize için olmalıdır . Bu, kısa ve uzun seçeneklerin karıştırılmasına izin vermez.
Adam Katz

7

In ksh93, getoptsuzun isimleri destekliyor mu ...

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

Ya da bulduğum öğreticiler söyledi. Deneyin ve görün.


4
Bu ksh93'ün yerleşik getopts. Bu sözdiziminin yanı sıra, kısa bir eşdeğeri olmayan uzun seçeneklere ve daha fazlasına da izin veren daha karmaşık bir sözdizimine sahiptir.
jilles

2
Makul bir cevap. OP NE kabuğunu belirtmedi.
ghoti

6

Sadece arada sırada kabuk komut dosyaları yazıyorum ve uygulamadan düşüyorum, bu nedenle herhangi bir geri bildirim takdir ediliyor.

@Arvid Requate tarafından önerilen stratejiyi kullanarak bazı kullanıcı hatalarını fark ettik. Bir değer eklemeyi unutan bir kullanıcı yanlışlıkla bir sonraki seçeneğin adını değer olarak kabul eder:

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

"loglevel" değerinin "--toc = TRUE" olarak görülmesine neden olur. Bu önlenebilir.

Http://mwiki.wooledge.org/BashFAQ/035 adresinden CLI için kullanıcı hatasını kontrol etme hakkında bazı fikirler uyarladım manuel ayrıştırma tartışmasından . Hem "-" hem de "-" bağımsız değişkenlerini işlemeye hata denetimi ekledim.

Sonra sözdizimi ile uğraşmaya başladım, bu yüzden buradaki hatalar kesinlikle benim hatam, orijinal yazarlar değil.

Yaklaşımım, eşit işaretli veya eşit işaretsiz uzun girmeyi tercih eden kullanıcılara yardımcı olur. Yani, "--loglevel 9" a "--loglevel = 9" ile aynı cevaba sahip olmalıdır. - / space yönteminde, kullanıcının bir argümanı unutup unutmadığından emin olmak mümkün değildir, bu nedenle bazı tahminler gereklidir.

  1. kullanıcı uzun / eşittir işaret biçimine (--opt =) sahipse, after = boşluğu, bağımsız değişken sağlanmadığından bir hatayı tetikler.
  2. kullanıcı uzun / boşluk bağımsız değişkenlerine (--opt) sahipse, bu komut dosyası hiçbir bağımsız değişken izlenmezse (komutun sonu) veya bağımsız değişken tire ile başlarsa başarısız olur.

Buna başlamanız durumunda, "--opt = değer" ve "--opt değeri" biçimleri arasında ilginç bir fark vardır. Eşittir işaretiyle, komut satırı bağımsız değişkeni "opt = değer" ve dize ayrıştırma işini "=" de ayırmak için iş olarak görülür. Aksine, "--opt value" ile, argümanın adı "opt" dur ve komut satırında bir sonraki değeri almakta zorlanıyoruz. @Arvid Requate dolaylı başvuru olan $ {! OPTIND} 'ı burada kullandı. Hala anlamıyorum, BashFAQ'daki yorumlar bu stile karşı uyarıyor gibi görünüyor ( http://mywiki.wooledge.org/BashFAQ/006 ). BTW, önceki posterin OPTIND = $ (($ OPTIND + 1)) önemi hakkındaki yorumlarının doğru olduğunu düşünmüyorum. Demek istiyorum,

Bu komut dosyasının en yeni sürümünde, flag -v VERBOSE çıktısı anlamına gelir.

"Cli-5.sh" adlı bir dosyaya kaydedin, yürütülebilir yapın ve bunlardan herhangi biri çalışacak veya istenen şekilde başarısız olacaktır.

./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

İşte kullanıcı intpu hata denetimi örnek çıktı

$ ./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

-V'yi açmayı düşünmelisiniz, çünkü OPTIND ve OPTARG'ın iç kısımlarını yazdırır

#/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"

OPTIND=$(( $OPTIND + 1 )): OPTIND parametresini her 'gobble' yaptığınızda gerekir (örneğin: biri kullanıldığında --toc value : değer $ OPTIND parametre numarasındadır. Toc'un değeri için aldığınızda, getopts'a ayrıştırılacak sonraki parametrenin değer olmadığını, ancak ondan sonraki (dolayısıyla :. ve betiğiniz (ve bahsettiğiniz kodun OPTIND=$(( $OPTIND + 1 )) yanı sıra) tamamlandıktan sonra eksik: shift $(( $OPTIND -1 ))(parametre 1'i OPTIND-1'e ayrıştırdıktan sonra getopts çıktıkça bunları kaydırmanız gerekir. $@artık "seçenek olmayan" parametrelerden biri
Olivier Dulac

oh, kendinizi kaydırdıkça, getopts altındaki parametreleri "kaydırırsınız", bu yüzden OPTIND her zaman doğru şeyi işaret ediyor ... ama çok kafa karıştırıcı buluyorum. Döngü sırasında getopts sonra hala $ (($ OPTIND - 1)) shift $ (şimdi $ $ (bir seçenek) işaret etmiyor ki), (şimdi $ komut (bir seçenek) işaret ama kalan argümanların ilkine (tüm seçeneklerden sonra gelenler ve değerleri). ör .: myrm -foo -bar = baz thisarg ththhisone thenanother
Olivier Dulac

5

Tekerleğin başka bir versiyonunu daha icat ediyor ...

Bu işlev GNU getopt için (umarım) POSIX uyumlu düz bourne kabuk değişimidir. Zorunlu / isteğe bağlı / bağımsız değişkenleri kabul edebilen kısa / uzun seçenekleri destekler ve seçeneklerin belirtilme biçimi GNU getopt ile neredeyse aynıdır, bu nedenle dönüşüm önemsizdir.

Tabii ki bu hala bir betiğe bırakmak için oldukça büyük bir kod yığınıdır, ancak iyi bilinen getopt_long kabuk işlevinin yaklaşık yarısıdır ve sadece mevcut GNU getopt kullanımlarını değiştirmek istediğiniz durumlarda tercih edilebilir.

Bu oldukça yeni bir kod, bu yüzden YMMV (ve bunun herhangi bir nedenle aslında POSIX uyumlu olmadığını kesinlikle bana bildirin - taşınabilirlik başlangıçtaki niyetti, ancak yararlı bir POSIX test ortamım yok).

Kod ve örnek kullanım aşağıdaki gibidir:

#!/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
}

Örnek kullanım:

# 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

4

Kabul edilen cevap, bash yerleşik tüm eksikliklerini belirtmek için çok güzel bir iş çıkarır getopts. Cevap şununla biter:

Bu nedenle, uzun seçenekler için destek eksikliği etrafında çalışmak için daha fazla kod yazmak mümkün olsa da, bu çok daha fazla iştir ve kodunuzu basitleştirmek için bir getopt ayrıştırıcı kullanma amacını kısmen yener.

Prensip olarak bu ifadeyi kabul etsem de, hepimizin bu özelliği çeşitli senaryolarda uygulama sayısının "standart", iyi test edilmiş bir çözüm oluşturmak için biraz çaba sarf etmeyi haklı olduğunu hissediyorum.

Bu nedenle, hiçbir harici bağımlılık olmadan, saf bash getoptsuygulayarak inşa bash "yükseltme" getopts_long. Fonksiyonun kullanımı dahili ile% 100 uyumludur getopts.

Bir komut dosyasına getopts_long( GitHub'da barındırılır ) dahil edilerek, orijinal sorunun yanıtı aşağıdaki gibi uygulanabilir:

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

3

Çözümünü yorumlamak veya oylamak için henüz yeterli temsilcim yok, ama sme'nin cevabı benim için çok iyi çalıştı. Ben içine koştu tek sorun argümanları tek tırnak (böylece onları bir şerit var) sarılmış sona erdi oldu.

Ayrıca bazı örnek kullanımlar ve YARDIM metni ekledim. Biraz genişletilmiş sürümümü buraya ekleyeceğim:

#!/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

3

Burada bash'de karmaşık seçenek ayrıştırma için birkaç farklı yaklaşım bulabilirsiniz: http://mywiki.wooledge.org/ComplexOptionParsing

Aşağıdakini oluşturdum ve iyi bir kod olduğunu düşünüyorum, çünkü minimum kod ve hem uzun hem de kısa seçenekler işe yarıyor. Uzun bir seçeneğin bu yaklaşımla birden fazla argümanı da olabilir.

#!/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

2

Bu konu üzerinde uzun zamandır çalışıyorum ... ve ana senaryonuzda kaynak göstermeniz gereken kendi kütüphanemi yaptım. Örnek için libopt4shell ve cd2mpc'ye bakınız . Umarım yardımcı olur !


2

Geliştirilmiş bir çözüm:

# 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

2

Uzun komut satırı seçeneklerine ihtiyaç duyulursa, sadece getopts kısmı için ksh kullanmak daha kolaydır, çünkü orada daha kolay yapılabilir.

# 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

+1 - Bunun açık kaynak AST projesinden (AT&T Research) ksh93 ile sınırlı olduğunu unutmayın.
Henk Langeveld

2

Dış bağımlılıkları olmayan, sıkı bash desteğiyle (-u) bir şey istedim ve daha eski bash sürümlerinde bile çalışması gerekiyordu. Bu çeşitli param türlerini ele alır:

  • kısa bools (-h)
  • kısa seçenekler (-i "image.jpg")
  • uzun bools (--help)
  • eşittir seçenekler (--file = "dosyaadı.ext")
  • alan seçenekleri (--file "dosyaadı.ext")
  • bitişik bools (-hvm)

Sadece betiğinizin üstüne şunu ekleyin:

# 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
}

Ve şu şekilde kullanın:

# 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 $*

1

Platformlar arası uyumlu kalmak ve harici yürütülebilir dosyalara güvenmekten kaçınmak için başka bir dilden bazı kodlar taşıdım.

Kullanımı çok kolay buluyorum, işte bir örnek:

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 )"

Gerekli BASH olabileceğinden biraz daha uzun, ama BASH 4'ün ilişkilendirilebilir dizilerine güvenmekten kaçınmak istedim. Bunu doğrudan http://nt4.com/bash/argparser.inc.sh adresinden de indirebilirsiniz.

#!/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

1

Tüm uzun seçenekleriniz kısa seçenekler olarak benzersiz ve eşleşen ilk karakterlere sahipse, örneğin

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

Aynıdır

./slamm -c 23 -p test -q

Getopts'tan önce $ args'yi yeniden yazmak için bunu kullanabilirsiniz :

# 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
    .....

İlham için mtvee için teşekkürler ;-)


Burada eval önemini alamadım
user.friendly

1

eğer betiği bu şekilde çağırmak istiyorsanız

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

o zaman getopt ve --longoptions yardımıyla bunu başarmanın en basit yolunu takip edebilirsiniz.

bunu dene, umarım bu faydalı

# 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

0

getopts, argümanları olmasını beklemediğiniz sürece uzun seçenekleri ayrıştırmak için "kullanılabilir" ...

Bunu nasıl yapacağınız aşağıda açıklanmıştır:

$ 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

Uzun seçenek için bir parametre almak için OPTIND'ı kullanmaya çalışırsanız, getopts bunu ilk isteğe bağlı konum parametresi olarak kabul etmez ve diğer parametreleri ayrıştırmayı durdurur. Böyle bir durumda, basit bir vaka bildirimi ile manuel olarak ele almanız daha iyi olacaktır.

Bu "her zaman" işe yarar:

$ 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

Gerçi getopts kadar esnek değildir ve hata örnekleri kodunun çoğunu vaka örnekleri içinde kendiniz yapmanız gerekir ...

Ancak bu bir seçenektir.


Ancak uzun seçenekler genellikle argümanlar bekler. Ve bir hack olsa bile - çalışmasını sağlamak için daha fazlasını yapabilirsiniz. Sonunda, eğer yerel olarak desteklemiyorsa, onu uygulamanın her yolunun bir kesmek olduğunu söyleyebiliriz, ancak yine de --'yi de genişletebilirsiniz . Ve evet vardiyası çok faydalıdır, ancak elbette bir argüman beklerse, bir sonraki argümanın (kullanıcı tarafından belirtilmemişse) beklenen argümanın bir parçası olduğu sonucuna varabilir.
Pryftan

evet, bu argümanlar olmadan uzun argümanlar adı için bir poc, ikisi arasında ayrım yapmak için getops gibi bir tür yapılandırmaya ihtiyacınız var. Ve vardiya ile ilgili olarak, her zaman set ile "geri koyabilirsiniz". Her durumda, bir parametrenin beklenip beklenmeyeceği yapılandırılabilir olmalıdır. Bunun için biraz sihir bile kullanabilirsiniz, ancak daha sonra kullanıcıları kullanmaya başlarsınız - sihir durur ve konumsal parametrelerin başladığını bildirir, bu da daha kötü imhodur.
estani

Yeterince adil. Bu daha makul. Tbh Neler yaşadığımı bile hatırlamıyorum ve bu sorunun kendisini tamamen unutmuştum. Sadece nasıl bulduğumu bile belirsiz bir şekilde hatırlıyorum. Şerefe. Oh ve bu fikir için +1. Çabadan geçtiniz ve ne elde ettiğinizi de netleştirdiniz. Başkalarına fikir verme çabası içinde olan insanlara saygı duyuyorum vb.
Pryftan

0

yerleşik getopts (ksh93 hariç) sadece ayrıştırma kısa seçenekler, ama yine de getopts kolları uzun seçenekler yapmak için komut dosyası birkaç satır ekleyebilirsiniz.

Http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts içinde bulunan kodun bir kısmı

  #== 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))

İşte bir test:

# 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

Aksi takdirde son Korn Shell ksh93, getoptsdoğal olarak uzun seçenekleri ayrıştırabilir ve hatta bir man sayfasını görüntüleyebilir. (Bkz. Http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )


0

Th yerleşik OS X (BSD) getopt uzun seçenekleri desteklemez, ancak GNU versiyonunda olduğu gibi: brew install gnu-getopt. Ardından benzer bir şey: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.


0

EasyOptions kısa ve uzun seçenekleri yönetir:

## 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.