Come analizzo gli argomenti della riga di comando in Bash?

Di ‘, ho uno script che viene chiamato con questa linea:

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

o questo:

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

Qual è il modo accettato di analizzarlo in modo tale che in ogni caso (o una combinazione dei due) $v , $f e $d saranno tutti impostati su true e $outFile sarà uguale a /fizz/someOtherFile ?

Metodo 1: Usare bash senza getopt [s]

Due modi comuni per passare argomenti key-value-pair sono:

Bash Space-Separated (ad esempio, --option argument ) (senza getopt [s])

Uso ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts

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

Bash Equals-Separated (es. --option=argument ) (senza getopt [s])

Uso ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

 #!/bin/bash for i in "[email protected]" 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 "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 

Per capire meglio ${i#*=} cerca “Rimozione sottostringa” in questa guida . È funzionalmente equivalente a `sed 's/[^=]*=//' <<< "$i"` che chiama un sottoprocesso inutile o `echo "$i" | sed 's/[^=]*=//'` `echo "$i" | sed 's/[^=]*=//'` che chiama due sottoprocessi inutili.

Metodo n. 2: utilizzo di bash con getopt [s]

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

getopt (1) limitazioni (versioni getopt più vecchie e relativamente recenti):

  • non può gestire argomenti che sono stringhe vuote
  • non può gestire argomenti con spazi bianchi incorporati

Le versioni più recenti di getopt non hanno queste limitazioni.

Inoltre, la shell POSIX (e altri) offrono getopts che non ha queste limitazioni. Ecco un esempio semplicistico di getopts :

 #!/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: [email protected]" # End of file 

I vantaggi di getopts sono:

  1. È più portatile e funzionerà in altre shell come dash .
  2. Può gestire più opzioni singole come -vf filename nel tipico modo Unix, automaticamente.

Lo svantaggio di getopts è che può gestire solo opzioni brevi ( -h , non --help ) senza codice aggiuntivo.

C'è un tutorial getopts che spiega cosa significano tutte le syntax e le variabili. In bash, c'è anche help getopts , che potrebbe essere informativo.

Nessuna risposta menziona getopt migliorato . E la risposta più votata è fuorviante: ignora le opzioni brevi di stile -⁠vfd (richieste dall’OP), le opzioni dopo gli argomenti posizionali (richieste anche dall’OP) e ignora gli errori di analisi. Anziché:

  • Usa getopt avanzato da util-linux o precedentemente GNU glibc . 1
  • Funziona con getopt_long() la funzione C di GNU glibc.
  • Ha tutte le caratteristiche distintive utili (le altre non le hanno):
    • gestisce gli spazi, citando caratteri e persino binario negli argomenti 2
    • può gestire le opzioni alla fine: script.sh -o outFile file1 file2 -v
    • consente le opzioni script.sh --outfile=fileOut --infile fileIn : script.sh --outfile=fileOut --infile fileIn
  • È così vecchio già 3 che non manca questo sistema GNU (ad es. Qualsiasi Linux lo abbia).
  • Puoi testarne l’esistenza con: getopt --test → return value 4.
  • Altri getopt o getopts shell sono di uso limitato.

Le seguenti chiamate

 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 

tutti ritornano

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

con il seguente myscript

 #!/bin/bash # saner programming env: these switches turn some bugs into errors set -o errexit -o pipefail -o noclobber -o nounset ! 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 # -use ! and PIPESTATUS to get exit code with errexit set # -temporarily store output to be able to check for errors # -activate quoting/enhanced mode (eg by writing out “--options”) # -pass arguments only via -- "[email protected]" to separate them correctly ! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "[email protected]") if [[ ${PIPESTATUS[0]} -ne 0 ]]; then # eg 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=nf=nv=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" 

1 getopt migliorato è disponibile sulla maggior parte dei “sistemi bash”, incluso Cygwin; su OS X prova brew install gnu-getopt o sudo port install getopt
2 le convenzioni exec() POSIX non hanno un modo affidabile per passare NULL binario negli argomenti della riga di comando; quei byte terminano prematuramente l’argomento
3 prima versione rilasciata nel 1997 o prima (l’ho rintracciata solo nel 1997)

da: digitalpeer.com con piccole modifiche

Uso myscript.sh -p=my_prefix -s=dirname -l=libname

 #!/bin/bash for i in "[email protected]" 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} 

Per capire meglio ${i#*=} cerca “Rimozione sottostringa” in questa guida . È funzionalmente equivalente a `sed 's/[^=]*=//' <<< "$i"` che chiama un sottoprocesso inutile o `echo "$i" | sed 's/[^=]*=//'` `echo "$i" | sed 's/[^=]*=//'` che chiama due sottoprocessi inutili.

getopt() / getopts() è una buona opzione. Rubato da qui :

Il semplice uso di “getopt” è mostrato in questo mini-script:

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

Quello che abbiamo detto è che qualsiasi -a, -b, -c o -d sarà permesso, ma che -c è seguito da un argomento (il “c:” dice quello).

Se chiamiamo questa “g” e proviamo:

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

Iniziamo con due argomenti e “getopt” separa le opzioni e inserisce ognuna nel proprio argomento. Inoltre ha aggiunto “-“.

A rischio di aggiungere un altro esempio da ignorare, ecco il mio schema.

  • handle -n arg e --name=arg
  • consente argomenti alla fine
  • mostra errori sensati se qualcosa è errato
  • compatibile, non usa i bashismi
  • leggibile, non richiede il mantenimento dello stato in un ciclo

Spero sia utile a qualcuno.

 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 

Modo più succinto

script.sh

 #!/bin/bash while [[ "$#" > 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" 

Uso:

 ./script.sh -d dev -u # OR: ./script.sh --deploy dev --uglify 

Sono in ritardo di circa 4 anni per questa domanda, ma voglio dare indietro. Ho usato le risposte precedenti come punto di partenza per riordinare la mia vecchia param parazione ad hoc. Ho quindi rifatto il seguente codice di modello. Gestisce sia i parametri lunghi che quelli brevi, usando argomenti = o separati dallo spazio, oltre a più parametri brevi raggruppati insieme. Infine inserisce nuovamente qualsiasi argomento non param in variabili $ 1, $ 2 .. Spero sia utile

 #!/usr/bin/env bash # NOTICE: Uncomment if your script depends on bashisms. #if [ -z "$BASH_VERSION" ]; then bash $0 [email protected] ; 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 

La mia risposta è in gran parte basata sulla risposta di Bruno Bronosky , ma ho praticamente schiacciato le sue due purissime implementazioni bash in una che uso molto spesso.

 # 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 

Ciò consente di avere sia opzioni / valori separati dallo spazio, sia valori uguali definiti.

Quindi puoi eseguire il tuo script usando:

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

così come:

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

ed entrambi dovrebbero avere lo stesso risultato finale.

PROFESSIONISTI:

  • Consente sia -arg = valore che -arg valore

  • Funziona con qualsiasi nome arg che puoi usare in bash

    • Significato -a o -arg o –arg o -arg o qualsiasi altra cosa
  • Pure bash. Non c’è bisogno di imparare / usare getopt o getopts

CONS:

  • Imansible combinare argomenti

    • Significato no -abc. Devi fare -a -b -c

Questi sono gli unici pro / contro che riesco a pensare in cima alla mia testa

Ho trovato il problema di scrivere l’analisi portatile negli script in modo così frustrante che ho scritto Argbash , un generatore di codice FOSS in grado di generare il codice di analisi degli argomenti per il tuo script, oltre ad avere alcune caratteristiche interessanti:

https://argbash.io

Penso che questo sia abbastanza semplice da usare:

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

Esempio di invito:

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

Espandendo sulla risposta eccellente di @guneysus, ecco un tweak che consente all’utente di utilizzare qualsiasi syntax che preferiscono, ad esempio

 command -x=myfilename.ext --another_switch 

vs

 command -x myfilename.ext --another_switch 

Vale a dire che gli uguali possono essere sostituiti con spazi bianchi.

Questa “interpretazione fuzzy” potrebbe non essere di vostro gradimento, ma se state realizzando script che sono intercambiabili con altre utilità (come nel mio caso, che deve funzionare con ffmpeg), la flessibilità è utile.

 STD_IN=0 prefix="" key="" value="" for keyValue in "[email protected]" 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 

getopts funziona alla grande se # 1 lo hai installato e # 2 hai intenzione di eseguirlo sulla stessa piattaforma. OSX e Linux (per esempio) si comportano diversamente a questo riguardo.

Ecco una soluzione (non getopts) che supporta flag uguali, non-uguali e booleani. Ad esempio potresti eseguire il tuo script in questo modo:

 ./script --arg1=value1 --arg2 value2 --shouldClean # parse the arguments. COUNTER=0 ARGS=("[email protected]") 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 

Ecco come faccio in una funzione per evitare di far scappare i getopts nello stesso momento da qualche parte più in alto nello stack:

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

Ti do la funzione parse_params che analizzerà i parametri dalla riga di comando.

  1. È una pura soluzione Bash, senza utilità aggiuntive.
  2. Non inquina l’ambito globale.
  3. Restituisce senza sforzo semplici variabili da usare, su cui potresti creare ulteriore logica.
  4. La quantità di trattini prima dei parametri non ha importanza ( -all uguale a all=all -all uguale a all=all )

Lo script seguente è una dimostrazione di lavoro copia-incolla. Vedi la funzione show_use per capire come usare parse_params .

limitazioni:

  1. Non supporta parametri delimitati da spazi ( -d 1 )
  2. I nomi dei --any-param perderanno i trattini quindi --any-param e -anyparam sono equivalenti
  3. eval $(parse_params "[email protected]") deve essere utilizzato all’interno della funzione bash (non funzionerà nell’ambito globale)

 #!/bin/bash # Universal Bash parameter parsing # Parses equal sign separated params into local variables (--name=bob creates variable $name=="bob") # 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) # Parses un-named params into ${ARGV[*]} array # Additionally puts all named params raw into ${ARGN[*]} array # Additionally puts all standalone "option" params raw into ${ARGO[*]} array # @author Oleksii Chekulaiev # @version v1.4 (Jun-26-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 _escaped=${1/\*/\'\"*\"\'} # If equals delimited named parameter if [[ "$1" =~ ^..*=..* ]]; then # Add to named parameters array echo "ARGN+=('$_escaped');" # key is part before first = local _key=$(echo "$1" | cut -d = -f 1) # 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 if [[ "$_key" == "" ]]; then shift continue fi # 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" =~ ^\-. ]]; then # remove dashes local _key=${1//\-} # skip when key is empty if [[ "$_key" == "" ]]; then 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 "[email protected]") # -- 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 

EasyOptions non richiede alcuna analisi:

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

Mi piacerebbe offrire la mia versione di analisi delle opzioni, che consente quanto segue:

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

Permette anche questo (potrebbe essere indesiderato):

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

Devi decidere prima dell’uso se = deve essere usato su un’opzione o meno. Questo per mantenere il codice pulito (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 

Nota che getopt(1) stato un breve errore vivente di AT & T.

getopt è stato creato nel 1984 ma era già stato seppellito nel 1986 perché non era veramente utilizzabile.

Una prova del fatto che getopt è molto obsoleto è che la pagina man getopt(1) menziona ancora "$*" invece di "[email protected]" , che è stata aggiunta alla Bourne Shell nel 1986 insieme alla shell getopts(1) incorporata per trattare argomenti con spazi interni.

BTW: se sei interessato ad analizzare lunghe opzioni negli script di shell, potrebbe essere interessante sapere che l’implementazione getopt(3) di libc (Solaris) e ksh93 entrambi aggiunto un’implementazione di opzione lunga uniforms che supporta opzioni lunghe come alias per brevi opzioni. Ciò fa sì che ksh93 e Bourne Shell implementino un’interfaccia uniforms per le opzioni lunghe tramite getopts .

Un esempio di opzioni lunghe prese dalla pagina man di Bourne Shell:

getopts "f:(file)(input-file)o:(output-file)" OPTX "[email protected]"

mostra per quanto tempo gli alias di opzione possono essere utilizzati sia in Bourne Shell che in ksh93.

Vedi la pagina man di una recente Bourne Shell:

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

e la pagina man per getopt (3) di OpenSolaris:

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

e infine, la pagina man getopt (1) per verificare l’obsoleto $ *:

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

Mescolando argomenti posizionali e basati su flag

–param = arg (uguale delimitato)

Mixaggio libero di flag tra argomenti posizionali:

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

può essere realizzato con un approccio abbastanza conciso:

 # 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 (spazio delimitato)

È sempre più chiaro non mischiare gli --flag=value e --flag value .

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

Questo è un po ‘difficile da leggere, ma è ancora valido

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

fonte

 # 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 

Assume we create a shell script named test_args.sh as follow

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

After we run the following command:

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

The output would be:

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

Use module “arguments” from bash-modules

Esempio:

 #!/bin/bash . import.sh log arguments NAME="world" parse_arguments "-n|--name)NAME;S" -- "[email protected]" || { error "Cannot parse command line." exit 1 } info "Hello, $NAME!" 

This also might be useful to know, you can set a value and if someone provides input, override the default with that value..

myscript.sh -f ./serverlist.txt or just ./myscript.sh (and it takes defaults)

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

Another solution without getopt[s], POSIX, old Unix style

Similar to the solution Bruno Bronosky posted this here is one without the usage of getopt(s) .

Main differentiating feature of my solution is that it allows to have options concatenated together just like tar -xzf foo.tar.gz is equal to tar -x -z -f foo.tar.gz . And just like in tar , ps etc. the leading hyphen is optional for a block of short options (but this can be changed easily). Long options are supported as well (but when a block starts with one then two leading hyphens are required).

Code with example options

 #!/bin/sh echo echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from [email protected][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 

For the example usage please see the examples further below.

Position of options with arguments

For what its worth there the options with arguments don’t be the last (only long options need to be). So while eg in tar (at least in some implementations) the f options needs to be last because the file name follows ( tar xzf bar.tar.gz works but tar xfz bar.tar.gz does not) this is not the case here (see the later examples).

Multiple options with arguments

As another bonus the option parameters are consumed in the order of the options by the parameters with required options. Just look at the output of my script here with the command line abc XYZ (or -abc XYZ ):

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

Long options concatenated as well

Also you can also have long options in option block given that they occur last in the block. So the following command lines are all equivalent (including the order in which the options and its arguments are being processed):

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

All of these lead to:

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

Not in this solution

Optional arguments

Options with optional arguments should be possible with a bit of work, eg by looking forward whether there is a block without a hyphen; the user would then need to put a hyphen in front of every block following a block with a parameter having an optional parameter. Maybe this is too complicated to communicate to the user so better just require a leading hyphen altogether in this case.

Things get even more complicated with multiple possible parameters. I would advise against making the options trying to be smart by determining whether the an argument might be for it or not (eg with an option just takes a number as an optional argument) because this might break in the future.

I personally favor additional options instead of optional arguments.

Option arguments introduced with an equal sign

Just like with optional arguments I am not a fan of this (BTW, is there a thread for discussing the pros/cons of different parameter styles?) but if you want this you could probably implement it yourself just like done at http://mywiki.wooledge.org/BashFAQ/035#Manual_loop with a --long-with-arg=?* case statement and then stripping the equal sign (this is BTW the site that says that making parameter concatenation is possible with some effort but “left [it] as an exercise for the reader” which made me take them at their word but I started from scratch).

Other notes

POSIX-compliant, works even on ancient Busybox setups I had to deal with (with eg cut , head and getopts missing).

Solution that preserves unhandled arguments. Demos Included.

Ecco la mia soluzione. It is VERY flexible and unlike others, shouldn’t require external packages and handles leftover arguments cleanly.

Usage is: ./myscript -flag flagvariable -otherflag flagvar2

All you have to do is edit the validflags line. It prepends a hyphen and searches all arguments. It then defines the next argument as the flag name eg

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

The main code (short version, verbose with examples further down, also a version with erroring out):

 #!/usr/bin/env bash #shebang.io validflags="rate time number" count=1 for arg in [email protected] 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 

The verbose version with built in echo demos:

 #!/usr/bin/env bash #shebang.io rate=30 time=30 number=30 echo "all args [email protected]" validflags="rate time number" count=1 for arg in [email protected] do match=0 argval=$1 # argval=$(echo [email protected] | 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: [email protected]" shift $# echo "post final clear args: [email protected]" set -- $leftovers echo "all post set args: [email protected]" echo arg1: $1 arg2: $2 echo leftovers: $leftovers echo rate $rate time $time number $number 

Final one, this one errors out if an invalid -argument is passed through.

 #!/usr/bin/env bash #shebang.io rate=30 time=30 number=30 validflags="rate time number" count=1 for arg in [email protected] 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 

Pros: What it does, it handles very well. It preserves unused arguments which a lot of the other solutions here don’t. It also allows for variables to be called without being defined by hand in the script. It also allows prepopulation of variables if no corresponding argument is given. (See verbose example).

Cons: Can’t parse a single complex arg string eg -xcvf would process as a single argument. You could somewhat easily write additional code into mine that adds this functionality though.

The top answer to this question seemed a bit buggy when I tried it — here’s my solution which I’ve found to be more robust:

 boolean_arg="" arg_with_value="" while [[ $# -gt 0 ]] do key="$1" case $key in -b|--boolean-arg) boolean_arg=true shift ;; -a|--arg-with-value) arg_with_value="$2" shift shift ;; -*) echo "Unknown option: $1" exit 1 ;; *) arg_num=$(( $arg_num + 1 )) case $arg_num in 1) first_normal_arg="$1" shift ;; 2) second_normal_arg="$1" shift ;; *) bad_args=TRUE esac ;; esac done # Handy to have this here when adding arguments to # see if they're working. Just edit the '0' to be '1'. if [[ 0 == 1 ]]; then echo "first_normal_arg: $first_normal_arg" echo "second_normal_arg: $second_normal_arg" echo "boolean_arg: $boolean_arg" echo "arg_with_value: $arg_with_value" exit 0 fi if [[ $bad_args == TRUE || $arg_num < 2 ]]; then echo "Usage: $(basename "$0")   [--boolean-arg] [--arg-with-value VALUE]" exit 1 fi 

This example shows how to use getopt and eval and HEREDOC and shift to handle short and long parameters with and without a required value that follows. Also the switch/case statement is concise and easy to follow.

 #!/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, don't 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" -- "[email protected]") 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 

The most significant lines of the script above are these:

 OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]") 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 

Short, to the point, readable, and handles just about everything (IMHO).

Spero che aiuti qualcuno.

I have write a bash helper to write a nice bash tool

project home: https://gitlab.mbedsys.org/mbedsys/bashopts

esempio:

 #!/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 -of -d "First name" -t string -i -s -r bashopts_declare -n last_name -l last -ol -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 "[email protected]" # Process argument bashopts_process_args 

will give help:

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

enjoy 🙂

Here is my approach – using regexp.

  • no getopts
  • it handles block of short parameters -qwerty
  • it handles short parameters -q -w -e
  • it handles long options --qwerty
  • you can pass attribute to short or long option (if you are using block of short options, attribute is attached to the last option)
  • you can use spaces or = to provide attributes, but attribute matches until encountering hyphen+space “delimiter”, so in --q=qwe ty qwe ty is one attribute
  • it handles mix of all above so -oa -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute is valid

script:

 #!/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 } [email protected] 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 

Here is my improved solution of Bruno Bronosky’s answer using variable arrays.

it lets you mix parameters position and give you a parameter array preserving the order without the options

 #!/bin/bash echo [email protected] PARAMS=() SOFT=0 SKIP=() for i in "[email protected]" do case $i in -n=*|--skip=*) SKIP+=("${i#*=}") ;; -s|--soft) SOFT=1 ;; *) # unknown option PARAMS+=("$i") ;; esac done echo "SKIP = ${SKIP[@]}" echo "SOFT = $SOFT" echo "Parameters:" echo ${PARAMS[@]} 

Will output for example:

 $ ./test.sh parameter -s somefile --skip=.c --skip=.obj parameter -s somefile --skip=.c --skip=.obj SKIP = .c .obj SOFT = 1 Parameters: parameter somefile