Bash'de komut satırı bağımsız değişkenlerini nasıl ayrıştırabilirim?


1921

Diyelim ki, bu satırla çağrılan bir senaryom var:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

ya da bu:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

Ne her durumda bu tür ayrıştırma edilen yolu (veya her ikisinde birden) var $v, $fve $dtüm ayarlanır trueve $outFileeşit olacaktır /fizz/someOtherFile?


1
Zsh kullanıcıları için yapabileceğiniz zparseopts adında harika bir yerleşik vardır: Bunlardan biri kullanılırsa zparseopts -D -E -M -- d=debug -debug=dhem -dve hem --debugde $debugdizide echo $+debug[1]0 veya 1 değerini döndürür. Referans: zsh.org/mla/users/2011/msg00350.html
dezza

1
Gerçekten iyi bir öğretici: linuxcommand.org/lc3_wss0120.php . Özellikle "Komut Satırı Seçenekleri" örneğini seviyorum.
Gabriel Staples

Yanıtlar:


2675

Yöntem # 1: Getopt [s] olmadan bash kullanma

Anahtar / değer çifti bağımsız değişkenlerini iletmenin iki yaygın yolu şunlardır:

Bash Space-Separated (örn. --option argument) (Getopt [s] olmadan)

kullanım demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

yukarıdaki bloğu kopyalayıp yapıştırarak çıktı:

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com

Bash Eşit Ayrılmış (örn. --option=argument) (Getopt [s] olmadan)

kullanım demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

yukarıdaki bloğu kopyalayıp yapıştırarak çıktı:

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com

Daha iyi anlamak ${i#*=}için bu kılavuzda "Alt Dizeyi Kaldırma" araması yapın . İşlevsel olarak `sed 's/[^=]*=//' <<< "$i"`gereksiz alt süreci `echo "$i" | sed 's/[^=]*=//'`çağıran veya iki gereksiz alt süreci çağıran işlevine eşdeğerdir .

Yöntem # 2: Getopt [s] ile bash kullanma

from: http://mywiki.wooledge.org/BashFAQ/035#getopts

getopt (1) sınırlamaları (eski, nispeten yeni getoptsürümler):

  • boş dize olan bağımsız değişkenleri işleyemez
  • gömülü boşluk içeren argümanları işleyemiyor

Daha yeni getoptsürümlerde bu sınırlamalar yoktur.

Ayrıca, getoptsbu sınırlamaları olmayan POSIX kabuğu (ve diğerleri) sunar . Basit bir getoptsörnek ekledim .

kullanım demo-getopts.sh -vf /etc/hosts foo bar

cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar

yukarıdaki bloğu kopyalayıp yapıştırarak çıktı:

verbose=1, output_file='/etc/hosts', Leftovers: foo bar

Avantajları getopts:

  1. Daha taşınabilir ve diğer kabuklarda çalışacak dash.
  2. -vf filenameTipik Unix gibi otomatik olarak çoklu tekli seçenekleri işleyebilir .

Dezavantajı, ek kod olmadan getoptssadece kısa seçenekleri ( -hdeğil --help) işleyebilmesidir .

Tüm sözdizimi ve değişkenlerin ne anlama geldiğini açıklayan bir getopts öğreticisi var. Bash'da, help getoptsbilgilendirici olabilecek de vardır.


44
Bu gerçekten doğru mu? Wikipedia'ya göre, GNU'nun gelişmiş bir sürümü var ve getoptbunların tüm işlevlerini getoptsve ardından bazılarını içeriyor . man getoptUbuntu 13.04 getopt - parse command options (enhanced)adı olarak çıktı , bu yüzden bu gelişmiş sürümün artık standart olduğunu varsayıyorum.
Livven

47
Sisteminizde bir şeylerin belirli bir yol olması, "standart olmak" üzerine yapılan varsayımları temel almak için çok zayıf bir dayanaktır.
szablica

13
@Livven, bu getoptbir GNU yardımcı programı değil, bir parçası util-linux.
Stephane Chazelas

4
Eğer kullanırsanız -gt 0sayfanı kaldırmak shiftsonra esac, bütün genişletmek shift1 ile ve bu durumda eklemek: *) break;;Eğer sigara -Optionnal argümanlar işleyebilir. Örn: pastebin.com/6DJ57HTc
Nicolas Lacombe

2
Yankılamazsın –default. İlk örnekte, –defaultson argüman ise, işlenmemiş ( while [[ $# -gt 1 ]]while [[ $# -gt 0 ]]
isteğe bağlı

562

Hiçbir cevap getopt gelişmiş bahsetti . Ve en çok oy alan cevap yanıltıcı: Ya -⁠vfdstil kısa seçenekleri (OP tarafından talep edilir) ya da pozisyon argümanlarından sonra (OP tarafından talep edilir) seçenekleri yoksayar ; ve ayrıştırma hatalarını yok sayar. Yerine:

  • Geliştirilmiş kullanın getoptutil-linux veya eskiden GNU glibc'nin dan . 1
  • getopt_long()GNU glibc'nin C fonksiyonu ile çalışır .
  • Has tüm yararlı ayırt edici özellikleri (diğerleri onları yok):
    • argüman 2'de boşlukları, karakterleri ve hatta ikilileri işler (geliştirilmemiş getoptbunu yapamaz)
    • sonunda seçenekleri işleyebilir: script.sh -o outFile file1 file2 -v( getoptsbunu yapmaz)
    • izin verir- =tarzı uzun seçenekler: script.sh --outfile=fileOut --infile fileIn(kendi kendine ayrıştırma her ikisine de izin uzun)
    • kombine kısa seçeneklere izin verir, örneğin -vfd(kendi kendine ayrıştırma durumunda gerçek çalışma)
    • seçenek argümanlarına dokunmaya izin verir, örneğin -oOutfileveya-vfdoOutfile
  • Şimdiye kadar 3 eski , hiçbir GNU sistemi bu eksik (örneğin herhangi bir Linux var).
  • Varlığını test etmek için: getopt --test→ dönüş değeri 4.
  • Diğer getoptveya kabuk yerleşikler getoptssınırlı kullanımlıdır.

Aşağıdaki çağrılar

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

tüm dönüşler

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

Takip ederek myscript

#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

Cygwin dahil çoğu “bash sisteminde” 1 geliştirilmiş getopt mevcuttur; OS X'de brew install gnu-getopt veyasudo port install getopt
2'yi deneyinPOSIXexec()kuralları, komut satırı bağımsız değişkenlerinde ikili NULL iletmenin güvenilir bir yoluna sahip değildir; bu baytlar1997'de veya daha önce yayınlanan ilk
3 argümanı erken bitirir(sadece 1997'ye kadar izledim)


4
Bunun için teşekkürler. Uzun seçenekler için desteğe ihtiyacınız varsa ve Solaris'te değilseniz , en.wikipedia.org/wiki/Getopts adresindeki özellik tablosundan onaylamanız yeterlidir getopt.
johncip

4
Tek uyarı ile bir sarıcı komut dosyası için birkaç seçenek özel olabilir sargı komut dosyalarında rahatçagetopt kullanılamaz ve daha sonra sarılmış yürütülebilir, bozulmamış yürütülebilir komut dosyası geçirme inanıyorum inanıyorum . Diyelim ki bir paketleyicim var ve özel bir seçeneğim var , o zaman yapamam ve otomatik olarak geçtim ; Ben ihtiyacım yapmak . İşte benim çözümün üstünde benim uygulama . grepmygrep--foomygrepmygrep --foo -A 2-A 2grepmygrep --foo -- -A 2
Kaushal Modi

2
@bobpaul util-linux hakkındaki ifadeniz de yanlış ve yanıltıcı: paket Ubuntu / Debian'da “gerekli” olarak işaretlenmiş. Bu nedenle, her zaman kurulur. - Hangi dağıtımlardan bahsediyorsunuz (nerede kurulması gerektiğini söylüyorsunuz)?
Robert Siemer

3
Bunun Mac'te şu anki 10.14.3 sürümüne kadar çalışmadığını unutmayın. Gemilerin getopt 1999'dan BSD getopt olduğunu ...
jjj

2
@transang Boolean dönüş değerinin olumsuzlanması. Ve yan etkisi: bir komutun başarısız olmasına izin verin (aksi takdirde errexit programı yanlışlıkla iptal eder). - Senaryodaki yorumlar size daha fazlasını anlatır. Aksi takdirde:man bash
Robert Siemer

144

Daha özlü yol

script.sh

#!/bin/bash

while [[ "$#" -gt 0 ]]; do
    case $1 in
        -d|--deploy) deploy="$2"; shift ;;
        -u|--uglify) uglify=1 ;;
        *) echo "Unknown parameter passed: $1"; exit 1 ;;
    esac
    shift
done

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"

Kullanımı:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify

3
Ben bunu yapıyorum. Zorunda while [[ "$#" > 1 ]]Ben bir boolean bayrak hattı biten desteklemek istiyorlarsa ./script.sh --debug dev --uglify fast --verbose. Örnek: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
hfossli

12
Vaov! Basit ve temiz! Bunu şu şekilde kullanıyorum: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
hfossli

2
Bu, kaynakla uğraşmaktan veya insanların işlevselliğinizin gerçekte nerede başladığını merak etmesinden ziyade her komut dosyasına yapıştırmak çok daha hoştur.
RealHandy

Uyarı: bu yinelenen argümanları tolere eder, en son argüman geçerlidir. örneğin ./script.sh -d dev -d prodsonuçlanır deploy == 'prod'. Yine de kullandım: P :): +1:
yair

Bu (teşekkürler!) Kullanıyorum ama boş argüman değerine izin verir, örneğin ./script.sh -dbir hata oluşturmaz ama sadece $deployboş bir dize ayarlayın unutmayın.
EM0

137

from: digitalpeer.com ile küçük değişiklikler

kullanım myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

Daha iyi anlamak ${i#*=}için bu kılavuzda "Alt Dizeyi Kaldırma" araması yapın . İşlevsel olarak `sed 's/[^=]*=//' <<< "$i"`gereksiz alt süreci `echo "$i" | sed 's/[^=]*=//'`çağıran veya iki gereksiz alt süreci çağıran işlevine eşdeğerdir .


4
Temiz! Bu boşlukla ayrılmış argümanlar için işe yaramasa da mount -t tempfs .... Bir muhtemelen gibi bir şey üzerinden bu çözebilirsiniz while [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;;vb
Tobias Kienzler

3
Bu, -vfdstil kombine kısa seçenekleri işleyemez .
Robert Siemer

105

getopt()/ getopts()iyi bir seçenektir. Buradan çalındı :

"Getopt" un basit kullanımı bu mini komut dosyasında gösterilmiştir:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done

Söylediklerimiz -a, -b, -c veya -d'den herhangi birine izin verilecek, ancak bu -c'yi bir argüman izliyor ("c:" bunu söylüyor).

Buna "g" adını verirsek ve denersek:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

İki argümanla başlıyoruz ve "getopt" seçenekleri ayırıyor ve her birini kendi argümanına koyuyor. Ayrıca "-" ekledi.


4
Kullanımı $*bozuk kullanımıdır getopt. (Boşluklarla argümanları hortumlar.) Doğru kullanım için cevabımı görün .
Robert Siemer

Neden daha karmaşık hale getirmek istesin ki?
SDsolar

@Matt J, betiğin ilk kısmı (i için), $ i yerine "$ i" kullanırsanız, içindeki boşluklarla bağımsız değişkenleri işleyebilir. Getopts boşluklarla argümanları idare edemiyor gibi görünüyor. For i döngüsü üzerinden getopt kullanmanın avantajı nedir?
thebunnyrules

99

Yoksaymak için başka bir örnek ekleme riski altında benim şemam.

  • kolları -n argve--name=arg
  • sonunda argümanlara izin verir
  • yanlış yazılmış bir şey varsa aklı başında hatalar gösterir
  • uyumlu, bashisms kullanmaz
  • okunabilir, bir döngüde durumun korunması gerekmez

Umarım birisi için yararlıdır.

while [ "$#" -gt 0 ]; do
  case "$1" in
    -n) name="$2"; shift 2;;
    -p) pidfile="$2"; shift 2;;
    -l) logfile="$2"; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;

    -*) echo "unknown option: $1" >&2; exit 1;;
    *) handle_argument "$1"; shift 1;;
  esac
done

4
Gecikme için üzgünüm. Komut dosyamda, handle_argument işlevi seçenek olmayan tüm bağımsız değişkenleri alır. Bu satırı istediğiniz şeyle değiştirebilir, belki de *) die "unrecognized argument: $1"değişkenleri değişken olarak toplayabilirsiniz *) args+="$1"; shift 1;;.
bronson

İnanılmaz! Birkaç cevabı test ettim, ancak bu, birçok konum parametresi (bayraklardan önce ve sonra) da dahil olmak üzere tüm durumlar için işe yarayan tek şey
Guilherme Garnier

2
güzel özlü kod, ancak -n ve başka hiçbir arg kullanarak hata nedeniyle sonsuz döngüye neden olur shift 2, shiftbunun yerine iki kez verir shift 2. Düzenleme önerdi.
lauksas

42

Bu soruya yaklaşık 4 yıl geç kaldım, ama geri vermek istiyorum. Daha önceki cevapları eski adhoc param ayrışımı düzenlemek için bir başlangıç ​​noktası olarak kullandım. Daha sonra aşağıdaki şablon kodunu yeniden düzenledim. = Veya boşlukla ayrılmış bağımsız değişkenler kullanarak hem uzun hem de kısa parametreleri ve birlikte gruplandırılmış birden çok kısa parametreyi işler. Sonunda param olmayan argümanları $ 1, $ 2 değişkenlerine geri ekler. Umarım faydalıdır.

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT="$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE="$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done

Bu kod böyle argümanlarla seçenekleri işleyemez: -c1. Ve =kısa seçenekleri argümanlarından ayırmak olağandışı ...
Robert Siemer

2
Bu yararlı kod yığını ile iki sorunla karşılaştım: 1) "-c = foo" durumunda "shift" sonraki parametre yeme sona erer; ve 2) 'c' birleştirilebilir kısa seçenekler için "[cfr]" desenine dahil edilmemelidir.
sfnd

36

Komut dosyalarında taşınabilir ayrıştırma yazma konusunu bu kadar sinir bozucu yazdım ki, Argbash'i yazdınız - betiğiniz için argümanları ayrıştırma kodu oluşturabilen bir FOSS kod üreteci artı bazı güzel özelliklere sahip:

https://argbash.io


Argbash yazdığınız için teşekkürler, sadece kullandım ve iyi çalıştığını gördüm. Çoğunlukla argbash'a gittim çünkü OS X 10.11 El Capitan'da bulunan eski bash 3.x'i destekleyen bir kod üreticisi. Tek dezavantajı, kod üreteci yaklaşımının, bir kod çağırmaya kıyasla ana kodunuzda oldukça fazla kod anlamına gelmesidir.
RichVel

Argbash'i sadece sizin için özel olarak hazırlanmış ayrıştırma kütüphanesi üretecek şekilde kullanabilirsiniz, betiğinize dahil edebileceğiniz ya da ayrı bir dosyaya sahip olabileceğiniz ve sadece kaynaklayabileceğiniz. Bunu göstermek için bir örnek ekledim ve bunu dokümantasyonda da daha açık hale getirdim.
bubla

Bunu bildiğim iyi oldu. Bu örnek ilginç ama yine de çok açık değil - belki de oluşturulan komut dosyasının adını 'parse_lib.sh' veya benzeri olarak değiştirebilir ve ana komut dosyasının nerede aradığını gösterebilirsiniz (daha karmaşık kullanım durumu olan kaydırma komut dosyası bölümünde olduğu gibi).
RichVel

Sorunlar argbash'ın son sürümünde giderildi: Belgeler geliştirildi, bir hızlı başlangıç ​​argbash-init betiği tanıtıldı ve argbash'i çevrimiçi olarak argbash.io/generate
bubla

29

Cevabım büyük ölçüde Bruno Bronosky'nin cevabına dayanıyor , ancak iki saf bash uygulamasını oldukça sık kullandığım bir püre haline getirdim.

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key="$1"
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE="$1"
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done

Bu, hem boşlukla ayrılmış seçeneklere / değerlere hem de eşit tanımlı değerlere sahip olmanızı sağlar.

Böylece betiğinizi aşağıdakileri kullanarak çalıştırabilirsiniz:

./myscript --foo -b -o /fizz/file.txt

Hem de:

./myscript -f --bar -o=/fizz/file.txt

ve her ikisi de aynı sonuca sahip olmalıdır.

Artıları:

  • -Arg = değer ve -arg değerine izin verir

  • Bash içinde kullanabileceğiniz herhangi bir arg adıyla çalışır

    • -A veya -arg veya --arg veya -arg veya her neyi ifade eder
  • Saf bash. Getopt veya getopts öğrenmeye / kullanmaya gerek yok

EKSİLERİ:

  • Arg'lar birleştirilemiyor

    • Anlam yok -abc. -A -b -c yapmalısınız

Bunlar kafamın en üstünde düşünebileceğim tek artılar / eksiler


15

Bence bu kullanımı oldukça basit:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

Çağırma örneği:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile

1
Hepsini okudum ve bu benim tercih ettiğim. -a=1Argc stili olarak kullanmak istemiyorum . Önce ana seçeneği -seçenekleri, sonra da tek aralıklı özel seçenekleri koymayı tercih ederim -o option. Ben argvs okumak için en basit-vs-daha iyi bir yol arıyorum.
m3nda

Gerçekten iyi çalışıyor, ancak a: seçeneğine bir argüman iletirseniz, aşağıdaki seçeneklerin tümü argüman olarak alınır. Bu satırı ./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFilekendi komut dosyanızla kontrol edebilirsiniz . -d seçeneği d olarak ayarlanmamış:
m3nda

15

@Guneysus'un mükemmel cevabına genişleyerek, kullanıcının istedikleri sözdizimini kullanmasına izin veren bir ayar, ör.

command -x=myfilename.ext --another_switch 

vs

command -x myfilename.ext --another_switch

Yani, eşitler boşluk ile değiştirilebilir.

Bu "bulanık yorum" beğeninize uygun olmayabilir, ancak diğer yardımcı programlarla (ffmpeg ile çalışması gereken benimki gibi) değiştirilebilir komut dosyaları yapıyorsanız, esneklik yararlıdır.

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "$@"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done

13

Bu örnek, aşağıdaki gerekli bir değere sahip olan ve olmayan kısa ve uzun parametrelerin nasıl kullanılacağını getoptve evalve HEREDOCve shiftnasıl ele alınacağını gösterir . Ayrıca, switch / case deyimi kısa ve izlemesi kolaydır.

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, dont change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<-EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below

Yukarıdaki komut dosyasının en önemli satırları şunlardır:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Kısa, noktaya, okunabilir ve hemen hemen her şeyi idare eder (IMHO).

Umarım birine yardım eder.


1
Bu en iyi cevaplardan biri.
Bay Polywhirl

11

parse_paramsKomut satırından paramleri ayrıştıracak işlevi size veriyorum .

  1. Saf bir Bash çözümü, ek yardımcı program yok.
  2. Küresel kapsamı kirletmez.
  3. Daha fazla mantık oluşturabileceğiniz, kullanımı kolay değişkenleri zahmetsizce döndürür.
  4. Params önce tire miktarı önemli değil ( --alleşittir -alleşittir all=all)

Aşağıdaki komut dosyası bir kopyala yapıştır çalışma gösterisidir. show_useNasıl kullanılacağını anlamak için işleve bakın parse_params.

Sınırlamalar:

  1. Alanla sınırlandırılmış parametreleri desteklemez ( -d 1)
  2. Param adları kısa çizgi kaybeder --any-paramve -anyparameşdeğerdir
  3. eval $(parse_params "$@")bash fonksiyonu içinde kullanılmalıdır (global kapsamda çalışmaz)

#!/bin/bash

# Universal Bash parameter parsing
# Parse equal sign separated params into named local variables
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Puts un-named params as-is into ${ARGV[*]} array
# Additionally puts all named params as-is into ${ARGN[*]} array
# Additionally puts all standalone "option" params as-is into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4.1 (Jul-27-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
        _escaped=${1/\*/\'\"*\"\'}
        _escaped=${_escaped//\'/\\\'}
        _escaped=${_escaped//\"/\\\"}
        # If equals delimited named parameter
        nonspace="[^[:space:]]"
        if [[ "$1" =~ ^${nonspace}${nonspace}*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "$1" | cut -d = -f 1)
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key='$_val';"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        elif [[ "$1" =~ ^\-${nonspace}+ ]]; then
            # remove dashes
            local _key=${1//\-}
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "$@")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2

Demoyu kullanmak için bash betiğinize gelen show_use "$@"
paramleri

Temel olarak github.com/renatosilva/easyoptions'ın aynı şekilde yaptığını ancak bu fonksiyondan biraz daha büyük olduğunu öğrendim .
Oleksii Chekulaiev

10

EasyOptions herhangi bir ayrıştırma gerektirmez:

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi

Örnek komut dosyanızın üst kısmındaki yorumların, varsayılan kullanım yardımı dizesi ve bağımsız değişken özellikleri sağlamak için ayrıştırıldığını fark etmem bir dakika sürdü. Bu harika bir çözüm ve 2 yılda sadece 6 oy aldığı için üzgünüm. Belki de bu soru insanların farkına varamayacak kadar gömülüdür.
Metamorfik

Bir anlamda çözümünüz en iyisidir ("standart" seçenek sözdizimini desteklemeyen @ OleksiiChekulaiev'in dışında). Bunun nedeni, çözümünüzün komut dosyası yazarının her seçeneğin adını yalnızca bir kez belirtmesini gerektirmesidir . Diğer çözümlerin, kullanımda, 'durum' düzeninde ve değişkenin ayarında - 3 kez belirtilmesini gerektirmesi, beni sürekli rahatsız etti. Getopt'in bile bu sorunu var. Ancak, kod makinemde yavaş - Bash uygulaması için 0.11s, Ruby için 0.28s. Açık "while-case" ayrıştırma için 0.02s.
Metamorfik

Belki de C ile yazılmış daha hızlı bir sürüm istiyorum. Ayrıca, zsh ile uyumlu bir sürüm. Belki bu ayrı bir soruyu hak eder ("Standart uzun seçenek sözdizimini kabul eden ve seçenek adlarının bir kereden fazla yazılmasını gerektirmeyen Bash benzeri kabuklarda komut satırı bağımsız değişkenlerini ayrıştırmanın bir yolu var mı?").
Metamorfik

10

getopts # 1 yüklüyse ve # 2 aynı platformda çalıştırmak istiyorsanız harika çalışıyor. OSX ve Linux (örneğin) bu konuda farklı davranırlar.

Eşit, eşit olmayan ve boole bayraklarını destekleyen (getopts olmayan) bir çözüm. Örneğin, betiğinizi şu şekilde çalıştırabilirsiniz:

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done

8

Yığını daha yüksek bir yerde aynı anda çalıştırmak kırma önlemek için bir işlevi nasıl budur:

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}

8

@ Bruno-bronosky'nin cevabına genişleyerek, bazı yaygın biçimlendirmeleri işlemek için bir "önişlemci" ekledim:

  • genişletir --longopt=valİçine--longopt val
  • genişletir -xyzİçine-x -y -z
  • Destekler --Bayrakların sonunu göstermek için
  • Beklenmedik seçenekler için bir hata gösterir
  • Kompakt ve okunması kolay seçenekler düğmesi
#!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename $0) [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "$1" ]; then
    exit "$1"
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg="$1"; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg"); END_OF_OPT=1 ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}${1}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD="$1" ;;
    -u|--username)  shift; USERNAME="$1" ;;
    -n|--name)      shift; names+=("$1") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "$1" ;;
    *)              POSITIONAL+=("$1") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"

6

Cmdline argümanlarını (örneğin GNU getopt (taşınabilir değil) vs BSD (OSX) getopt vs getopts) ayrıştırmanın birkaç yolu vardır - hepsi sorunludur. Bu çözüm

  • taşınabilir!
  • sıfır bağımlılığı vardır, sadece bash yerleşiklerine dayanır
  • hem kısa hem de uzun seçeneklere izin verir
  • seçenek ve bağımsız değişken arasındaki boşlukları işler, ancak =ayırıcı da kullanabilir
  • sıralı kısa seçenek stilini destekler -vxf
  • isteğe bağlı bağımsız değişkenlerle işleme seçeneği (örneğe bakın) ve
  • aynı özellik kümesi için alternatiflerle karşılaştırıldığında kod bloat gerektirmez. Özlü ve bu nedenle bakımı daha kolay

Örnekler:

# flag
-f
--foo

# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"

# option with optional argument
--baz
--baz="Optional Hello"

#!/usr/bin/env bash

usage() {
  cat - >&2 <<EOF
NAME
    program-name.sh - Brief description

SYNOPSIS
    program-name.sh [-h|--help]
    program-name.sh [-f|--foo]
                    [-b|--bar <arg>]
                    [--baz[=<arg>]]
                    [--]
                    FILE ...

REQUIRED ARGUMENTS
  FILE ...
          input files

OPTIONS
  -h, --help
          Prints this and exits

  -f, --foo
          A flag option

  -b, --bar <arg>
          Option requiring an argument <arg>

  --baz[=<arg>]
          Option that has an optional argument <arg>. If <arg>
          is not specified, defaults to 'DEFAULT'
  --     
          Specify end of options; useful if the first non option
          argument starts with a hyphen

EOF
}

fatal() {
    for i; do
        echo -e "${i}" >&2
    done
    exit 1
}

# For long option processing
next_arg() {
    if [[ $OPTARG == *=* ]]; then
        # for cases like '--opt=arg'
        OPTARG="${OPTARG#*=}"
    else
        # for cases like '--opt arg'
        OPTARG="${args[$OPTIND]}"
        OPTIND=$((OPTIND + 1))
    fi
}

# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "$@")  # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
    case "$optchar" in
        h) usage; exit 0 ;;
        f) foo=1 ;;
        b) bar="$OPTARG" ;;
        -) # long option processing
            case "$OPTARG" in
                help)
                    usage; exit 0 ;;
                foo)
                    foo=1 ;;
                bar|bar=*) next_arg
                    bar="$OPTARG" ;;
                baz)
                    baz=DEFAULT ;;
                baz=*) next_arg
                    baz="$OPTARG" ;;
                -) break ;;
                *) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
            esac
            ;;
        *) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
    esac
done

shift $((OPTIND-1))

if [ "$#" -eq 0 ]; then
    fatal "Expected at least one required argument FILE" \
    "See '${0} --help' for usage"
fi

echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"

5

Aşağıdakilere izin veren seçenek ayrıştırma sürümümü sunmak istiyorum:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

Ayrıca buna izin verir (istenmeyen olabilir):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

Kullanmadan önce = bir seçenek üzerinde kullanılıp kullanılmayacağına karar vermelisiniz. Bu kodu temiz tutmaktır (ish).

while [[ $# > 0 ]]
do
    key="$1"
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE="$2"
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder="$2"
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done

1
$ {key + x} için "+ x" nin anlamı nedir?
Luca Davanzo

1
'Anahtar'ın mevcut olup olmadığını görmek için bir testtir. Daha aşağıda anahtarı ayarlamıyorum ve bu iç while döngüsünü kırar.
galmok

5

İşlenmemiş argümanları koruyan çözüm. Demolar Dahil.

İşte benim çözümüm. ÇOK esnektir ve diğerlerinden farklı olarak, harici paketler gerektirmez ve artık argümanları temiz bir şekilde işler.

Kullanımı: ./myscript -flag flagvariable -otherflag flagvar2

Tek yapmanız gereken, geçerli bayrak satırını düzenlemek. Bir tire işareti kullanır ve tüm bağımsız değişkenleri arar. Daha sonra bir sonraki argümanı bayrak adı olarak tanımlar;

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

Ana kod (kısa versiyon, daha ayrıntılı örnekler, ayrıca hata veren bir versiyon):

#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers

Yerleşik yankı demolarına sahip ayrıntılı sürüm:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
#   argval=$(echo $@ | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1: $1 arg2: $2

echo leftovers: $leftovers
echo rate $rate time $time number $number

Son olarak, geçersiz bir argüman iletilirse hata oluşur.

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
do
    argval=$1
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=$2
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers

Artıları: Ne yapar, çok iyi idare eder. Buradaki diğer çözümlerin çoğunun kullanmadığı argümanları korur. Ayrıca, değişkenlerin komut dosyasında elle tanımlanmadan çağrılmasını sağlar. Ayrıca karşılık gelen bir argüman verilmezse değişkenlerin ön popülasyonuna izin verir. (Ayrıntılı örneğe bakın).

Eksileri: Tek bir karmaşık arg dizesi ayrıştırılamıyor, örneğin -xcvf tek bir argüman olarak işlenir. Yine de bu işlevselliği ekleyen benimkine kolayca ek kod yazabilirsiniz.



3

Bunun getopt(1)AT&T tarafından kısa bir yaşam hatası olduğuna dikkat edin .

getopt 1984'te kuruldu, ancak 1986'da gömüldü çünkü gerçekten kullanışlı değildi.

Aslında bir kanıtı getoptçok modası geçmiş olmasıdır getopt(1)adam sayfa yine bahseder "$*"yerine "$@"birlikte 1986 yılında The Bourne Shell eklendiğini,getopts(1) içeride boşluklarla argümanları ile başa amacıyla kabuk yerleşiği.

BTW: kabuk komut dosyalarında uzun seçenekleri ayrıştırmakla ilgileniyorsanız getopt(3), libc (Solaris) uygulamasının ve ksh93her ikisinin de kısa seçenekler için takma adlar olarak uzun seçenekleri destekleyen tek tip bir uzun seçenek uygulamasını eklediğini bilmek ilgi çekici olabilir . Bu neden ksh93ve Bourne Shellaracılığıyla uzun seçenekler için tek tip bir arayüz uygulamakgetopts .

Bourne Shell man sayfasından alınan uzun seçeneklere bir örnek:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

, seçenek takma adlarının hem Bourne Kabuğu'nda hem de ksh93'te ne kadar süre kullanılabileceğini gösterir.

Yeni Bourne Kabuğunun kılavuz sayfasına bakınız:

http://schillix.sourceforge.net/man/man1/bosh.1.html

ve OpenSolaris'ten getopt (3) için man sayfası:

http://schillix.sourceforge.net/man/man3c/getopt.3c.html

ve son olarak, eskimiş $ * durumunu doğrulamak için getopt (1) kılavuz sayfası:

http://schillix.sourceforge.net/man/man1/getopt.1.html


3

Güzel bir bash aracı yazmak için bir bash yardımcısı yazdım

proje ana sayfası: https://gitlab.mbedsys.org/mbedsys/bashopts

misal:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "$@"

# Process argument
bashopts_process_args

yardım edecek:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

zevk almak :)


Bunu Mac OS X'te alıyorum: `` lib / bashopts.sh: line 138: declare: -A: geçersiz seçenek bildirimi: use: declare [-afFirtx] [-p] [ad [= değer] ...] Lib / bashopts.sh dosyasında hata: 138. 'declare -x -A bashopts_optprop_name' durum 2 ile çıkıldı Çağrı ağacı: 1: lib / controller.sh: 4 kaynak (...) Durum 1 ile çıkma ``
Josh Wulf

Bunu kullanmak için Bash sürüm 4'e ihtiyacınız var. Mac'te varsayılan sürüm 3'tür. Bash 4'ü yüklemek için home brew kullanabilirsiniz.
Josh Wulf

3

İşte benim yaklaşımım - regexp kullanarak.

  • getopts yok
  • kısa parametre bloğunu işler -qwerty
  • kısa parametreleri işler -q -w -e
  • uzun seçenekleri idare eder --qwerty
  • özniteliği kısa veya uzun seçeneğe geçirebilirsiniz (kısa seçeneklerin bloğunu kullanıyorsanız, özellik son seçeneğe eklenir)
  • boşlukları kullanabilir veya =nitelikler sağlayabilir, ancak nitelik + boşluk "sınırlayıcı" ile karşılaşana kadar eşleşir, yani --q=qwe ty qwe tybir özellik
  • Yukarıdakilerin karışımını işler, böylece -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ributegeçerlidir

senaryo:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
options=$@

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done

Bunun gibi. Belki de sadece yeni çizgiyle yankılamak için -e param ekleyin.
mauron85

3

Aşağıdaki test_args.shgibi bir kabuk betiği oluşturduğumuzu varsayalım

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

Aşağıdaki komutu çalıştırdıktan sonra:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

Çıktı:

year=2017 month=12 day=22 flag=true

5
Bu, Noah'ın cevabı ile aynı yaklaşımı benimser , ancak daha az güvenlik kontrolü / güvencesi vardır. Bu, komut dosyasının ortamına rastgele argümanlar yazmamızı sağlar ve eminim burada eval kullanmanız komut enjeksiyonuna izin verebilir.
Will Barnwell

2

Bash modüllerinden "argümanlar" modülünü kullanın

Misal:

#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "$@" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"

2

Konumsal ve bayrak tabanlı bağımsız değişkenleri karıştırma

--param = arg (sınırlandırılmış)

Konumsal argümanlar arasında bayrakları serbestçe karıştırma:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

oldukça özlü bir yaklaşımla gerçekleştirilebilir:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

--param arg (boşlukla ayrılmış)

Karışık --flag=valueve --flag valuestilleri karıştırmamak her zaman daha açıktır .

./script.sh dumbo 127.0.0.1 --environment production -q -d

Bu okunması biraz zor ama yine de geçerli

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

Kaynak

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

2

Burada, kodlamayı minimum kodla gerçekleştiren ve alt dize ile eval kullanarak bir durumda ne çıkarmak istediğinizi tanımlamanıza izin veren bir getopts.

temel olarak eval "local key='val'"

function myrsync() {

        local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k="$1";
                case "$k" in
                    ---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
                        eval "local ${k:3}='${2}'"; shift; shift    # Past two arguments
                    ;;
                    *)  # Unknown option  
                        args+=("$1"); shift;                        # Past argument only
                    ;;                                              
                esac                                                
        done; set -- "${backup[@]}"                                 # Restore $@


        echo "${sourceurl}"
}

Değişkenleri burada en çok yanıt olarak global yerine yerel olarak bildirir.

Olarak adlandırılan:

myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ... 

$ {K: 3} temelde birinciyi ---anahtardan kaldırmak için kullanılan bir alt dizedir .


1

Bu da bilmek yararlı olabilir, bir değer ayarlayabilirsiniz ve birisi girdi sağlarsa, bu değerle varsayılan değeri geçersiz kılın.

myscript.sh -f ./serverlist.txt veya yalnızca ./myscript.sh (ve varsayılanları alır)

    #!/bin/bash
    # --- set the value, if there is inputs, override the defaults.

    HOME_FOLDER="${HOME}/owned_id_checker"
    SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

    while [[ $# > 1 ]]
    do
    key="$1"
    shift

    case $key in
        -i|--inputlist)
        SERVER_FILE_LIST="$1"
        shift
        ;;
    esac
    done


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"

1

Getopt [s], POSIX, eski Unix stili olmayan başka bir çözüm

Bruno Bronosky bu çözüm gönderdi benzer bir kullanımı olmadangetopt(s) .

Çözümümün ana ayırt edici özelliği, tıpkı tar -xzf foo.tar.gzeşit olduğu gibi birlikte birleştirilmiş seçeneklere sahip olmasına izin vermesidir tar -x -z -f foo.tar.gz. Ve tıpkı içindeki gibi tar,ps vs.'de kısa çizgi bir kısa seçenek bloğu için isteğe bağlıdır (ancak bu kolayca değiştirilebilir). Uzun seçenekler de desteklenir (ancak bir blok bir tane ile başladığında iki önde gelen tire gereklidir).

Örnek seçeneklerle kodlama

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"
echo

print_usage() {
  echo "Usage:

  $0 {a|b|c} [ARG...]

Options:

  --aaa-0-args
  -a
    Option without arguments.

  --bbb-1-args ARG
  -b ARG
    Option with one argument.

  --ccc-2-args ARG1 ARG2
  -c ARG1 ARG2
    Option with two arguments.

" >&2
}

if [ $# -le 0 ]; then
  print_usage
  exit 1
fi

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  elif [ ! "$opt" ]; then

    # we are at the beginning of a fresh block
    # remove optional leading hyphen and strip trailing whitespaces
    opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

  fi

  # get the first character -> check whether long option
  first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
  [ "$first_chr" = - ] && long_option=T || long_option=F

  # note to write the options here with a leading hyphen less
  # also do not forget to end short options with a star
  case $opt in

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

      if [ "$2" ]; then
        echo "Option BBB with argument '$2' activated!"
        shift
      else
        echo "BBB parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    c*|-ccc-2-args)

      if [ "$2" ] && [ "$3" ]; then
        echo "Option CCC with arguments '$2' and '$3' activated!"
        shift 2
      else
        echo "CCC parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

      if [ "$long_option" = T ]; then
        opt=$(echo "$opt" | awk '{print substr($1, 2)}')
      else
        opt=$first_chr
      fi
      printf 'Error: Unknown option: "%s"\n' "$opt" >&2
      print_usage
      exit 1
      ;;

  esac

  if [ "$long_option" = T ]; then

    # if we had a long option then we are going to get a new block next
    shift
    opt=

  else

    # if we had a short option then just move to the next character
    opt=$(echo "$opt" | awk '{print substr($1, 2)}')

    # if block is now empty then shift to the next one
    [ "$opt" ] || shift

  fi

done

echo "Doing something..."

exit 0

Örnek kullanım için lütfen aşağıdaki örneklere bakın.

Bağımsız değişkenli seçeneklerin konumu

Orada değer ne argümanlar ile seçenekler son değildir (sadece uzun seçenekler olması gerekir). Bu nedenle, örneğin tar(en azından bazı uygulamalarda) fseçeneklerin son olması gerekir, çünkü dosya adı aşağıdaki gibidir ( tar xzf bar.tar.gzçalışır, ancak tar xfz bar.tar.gzçalışmaz) bu durum böyle değildir (sonraki örneklere bakın).

Bağımsız değişkenli çoklu seçenekler

Başka bir bonus olarak opsiyon parametreleri, gerekli seçeneklere sahip parametreler tarafından seçenekler sırasına göre tüketilir. Komut satırı abc X Y Z(veya -abc X Y Z) ile betiğimin çıktısına bakın :

Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!

Uzun seçenekler de birleştirildi

Ayrıca, blokta en son meydana geldikleri göz önüne alındığında, seçenek bloğunda uzun seçeneklere sahip olabilirsiniz. Dolayısıyla, aşağıdaki komut satırlarının tümü eşdeğerdir (seçeneklerin ve bağımsız değişkenlerinin işlenme sırası dahil):

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

Bunların hepsi şunlara yol açar:

Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...

Bu çözümde değil

İsteğe bağlı argümanlar

İsteğe bağlı argümanlara sahip seçenekler biraz çalışma ile mümkün olmalıdır, örneğin tire işareti olmayan bir blok olup olmadığını ileriye bakarak; daha sonra kullanıcının, isteğe bağlı bir parametresi olan bir parametreyle bir bloğu izleyen her bloğun önüne bir tire işareti koyması gerekir. Belki de bu, kullanıcıyla iletişim kurmak için çok karmaşık olduğundan, bu durumda önde gelen bir kısa çizgi gerektirir.

Olası çoklu parametrelerle işler daha da karmaşıklaşıyor. Ben bir argüman bunun için olup olmadığını belirleyerek akıllı olmaya çalışırken seçenekleri yapmaya karşı öneriyoruz (örneğin bir seçenek ile sadece isteğe bağlı bir argüman olarak bir sayı alır) çünkü bu gelecekte kırılabilir.

Ben şahsen isteğe bağlı argümanlar yerine ek seçenekleri tercih ediyorum.

Eşittir işaretiyle sunulan seçenek bağımsız değişkenleri

Tıpkı isteğe bağlı argümanlarda olduğu gibi bunun hayranı değilim (BTW, farklı parametre stillerinin artılarını / eksilerini tartışmak için bir konu var mı?) Ama bunu isterseniz, muhtemelen http: // mywiki.wooledge.org/BashFAQ/035#Manual_loop ile bir --long-with-arg=?*case deyimi ve daha sonra eşittir işaretini sıyırma (bu, BTW'nin parametre birleştirmesinin biraz çaba ile mümkün olduğunu söyleyen site ama okuyucu için bir egzersiz olarak "bıraktı" "bu da onları sözlerine götürdü ama sıfırdan başladım).

Diğer notlar

POSIX uyumlu, (örneğin hatta ben uğraşmak zorunda antik Busybox kurulumları çalışır cut, headve getoptseksik).

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.