Utilizzo di getopts per elaborare le opzioni della riga di comando lunghe e brevi


410

Vorrei avere forme lunghe e brevi di opzioni della riga di comando invocate usando il mio script shell.

So che getoptspuò essere usato, ma come in Perl, non sono stato in grado di fare lo stesso con shell.

Qualche idea su come farlo, in modo che io possa usare opzioni come:

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

In quanto sopra, entrambi i comandi significano la stessa cosa per la mia shell, ma usando getopts, non sono stato in grado di implementarli?


2
IMHO, la risposta accettata non è la migliore. Non mostra come usare getopts per gestire entrambi gli argomenti "-" e "-", cosa che può essere fatta, come dimostrato da @Arvid Requate. Sto inserendo un'altra risposta usando un concetto simile, ma mi occupo anche dell'errore dell'utente di "dimenticare" di inserire valori per gli argomenti necessari. Punto chiave: getopts può funzionare. L'utente dovrebbe evitare di usare "getopt" invece se è necessaria la portabilità multipiattaforma. Inoltre, getopts fa parte dello standard POSIX per le shell, quindi è probabile che sia portatile.
pauljohn32,

Risposte:


304

Esistono tre implementazioni che possono essere prese in considerazione:

  • Bash incorporato getopts. Questo non supporta nomi di opzioni lunghe con il prefisso a doppio trattino. Supporta solo opzioni a carattere singolo.

  • Implementazione UNIX di BSD del getoptcomando autonomo (che è ciò che utilizza MacOS). Anche questo non supporta opzioni lunghe.

  • Implementazione GNU di standalone getopt. GNU getopt(3)(utilizzato dalla riga di comando getopt(1)su Linux) supporta l'analisi di opzioni lunghe.


Alcune altre risposte mostrano una soluzione per l'utilizzo del built-in bash getoptsper imitare le opzioni lunghe. Questa soluzione in realtà fa una breve opzione il cui carattere è "-". Quindi ottieni "-" come bandiera. Quindi tutto ciò che segue diventa OPTARG e si verifica OPTARG con un nidificato case.

Questo è intelligente, ma viene fornito con avvertenze:

  • getoptsimpossibile applicare la specifica opt. Non può restituire errori se l'utente fornisce un'opzione non valida. Devi fare il tuo controllo degli errori mentre analizzi OPTARG.
  • OPTARG viene utilizzato per il nome dell'opzione lunga, il che complica l'utilizzo quando la stessa opzione lunga ha un argomento. Alla fine devi codificarlo tu stesso come caso aggiuntivo.

Quindi, mentre è possibile scrivere più codice per ovviare alla mancanza di supporto per le opzioni lunghe, questo è molto più lavoro e parzialmente vanifica lo scopo dell'utilizzo di un parser getopt per semplificare il codice.


18
Così. Qual è la soluzione multipiattaforma portatile?
troelskn,

6
GNU Getopt sembra essere l'unica scelta. Su Mac, installa GNU getopt da macports. Su Windows, installerei GNU getopt con Cygwin.
Bill Karwin,

2
Apparentemente , getsh ksh può gestire opzioni lunghe.
Tgr

1
@Bill +1, anche se è anche abbastanza semplice creare getopt dal sorgente ( software.frodo.looijaard.name/getopt ) su Mac. Puoi anche controllare la versione di getopt installata sul tuo sistema dagli script con "getopt -T; echo $?".
Chinasaur,

8
@Bill Karwin: "Il comando incorporato bash getopts non supporta nomi di opzioni lunghi con il prefisso a doppio trattino." Ma getopts può essere fatto per supportare opzioni lunghe: vedi stackoverflow.com/a/7680682/915044 di seguito.
TomRoche,

307

getopte getoptssono bestie diverse, e le persone sembrano avere un po 'di incomprensioni su ciò che fanno. getoptsè un comando integrato bashper elaborare le opzioni della riga di comando in un ciclo e assegnare ciascuna opzione trovata e valore a sua volta alle variabili integrate, in modo da poterle ulteriormente elaborare. getopt, tuttavia, è un programma di utilità esterno e in realtà non elabora le opzioni per te come fanno bash getopts, il Getoptmodulo Perl o Python optparse/ module argparse. Tutto ciò che getoptfa è canonicalizzare le opzioni che vengono passate, ovvero convertirle in un modulo più standard, in modo che sia più facile per uno script shell elaborarle. Ad esempio, un'applicazione di getoptpotrebbe convertire quanto segue:

myscript -ab infile.txt -ooutfile.txt

in questo:

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

Devi eseguire tu stesso l'elaborazione effettiva. Non è necessario utilizzare getoptaffatto se si applicano varie restrizioni sul modo in cui è possibile specificare le opzioni:

  • metti solo un'opzione per argomento;
  • tutte le opzioni vanno prima di qualsiasi parametro posizionale (cioè argomenti non di opzione);
  • per le opzioni con valori (ad es. -osopra), il valore deve andare come argomento separato (dopo uno spazio).

Perché usare getoptinvece di getopts? Il motivo di base è che solo GNU getoptoffre supporto per le opzioni della riga di comando con nome lungo. 1 (GNU getoptè l'impostazione predefinita su Linux. Mac OS X e FreeBSD sono forniti di base e non molto utili getopt, ma la versione GNU può essere installata; vedi sotto).

Ad esempio, ecco un esempio dell'uso di GNU getopt, da un mio script chiamato javawrap:

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

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

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

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

Ciò consente di specificare opzioni simili --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"o simili. L'effetto della chiamata a getoptè di canonicalizzare le opzioni in --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"modo da poterle elaborare più facilmente. La citazione intorno "$1"ed "$2"è importante in quanto garantisce che gli argomenti con spazi siano gestiti correttamente.

Se elimini le prime 9 righe (tutto su attraverso la eval setriga), il codice continuerà a funzionare ! Tuttavia, il tuo codice sarà molto più selettivo in quale tipo di opzioni accetta: In particolare, dovrai specificare tutte le opzioni nel modulo "canonico" sopra descritto. Con l'uso di getopt, tuttavia, è possibile raggruppare opzioni a lettera singola, utilizzare forme più brevi non ambigue di opzioni lunghe, utilizzare lo stile --file foo.txto --file=foo.txt, utilizzare lo stile -m 4096o -m4096, mescolare opzioni e non opzioni in qualsiasi ordine, ecc. getoptgenera inoltre un messaggio di errore se vengono rilevate opzioni non riconosciute o ambigue.

NOTA : Esistono in realtà due versioni totalmente diverse di getopt, base getopte GNU getopt, con caratteristiche diverse e convenzioni di chiamata diverse. 2 Basic getoptè piuttosto rotto: non solo non gestisce le opzioni lunghe, ma non può nemmeno gestire spazi incorporati all'interno di argomenti o argomenti vuoti, mentre lo getoptsfa bene. Il codice sopra non funzionerà in base getopt. GNU getoptè installato di default su Linux, ma su Mac OS X e FreeBSD deve essere installato separatamente. Su Mac OS X, installa MacPorts ( http://www.macports.org ) e quindi esegui sudo port install getoptl'installazione di GNU getopt(di solito in /opt/local/bin) e assicurati che /opt/local/binsia nel percorso della shell prima di/usr/bin. Su FreeBSD, installa misc/getopt.

Una guida rapida alla modifica del codice di esempio per il tuo programma: Delle prime righe, tutto è "boilerplate" che dovrebbe rimanere lo stesso, tranne la linea che chiama getopt. È necessario modificare il nome del programma dopo -n, specificare le opzioni brevi dopo -oe le opzioni lunghe dopo --long. Inserisci i due punti dopo le opzioni che assumono un valore.

Infine, se vedi il codice che ha solo setinvece di eval set, è stato scritto per BSD getopt. Dovresti cambiarlo per usare lo eval setstile, che funziona bene con entrambe le versioni di getopt, mentre la pianura setnon funziona bene con GNU getopt.

1 In realtà, getoptsin ksh93supporti opzioni lunga di nome, ma questo guscio non è usato spesso come bash. In zsh, utilizzare zparseoptsper ottenere questa funzionalità.

2 Tecnicamente, "GNU getopt" è un termine improprio; questa versione è stata effettivamente scritta per Linux anziché per il progetto GNU. Tuttavia, segue tutte le convenzioni GNU e il termine "GNU getopt" è comunemente usato (ad esempio su FreeBSD).


3
Questo è stato molto utile, l'idea di usare getopt per controllare le opzioni e quindi elaborare quelle opzioni in un ciclo molto semplice ha funzionato molto bene quando volevo aggiungere lunghe opzioni di stile a uno script bash. Grazie.
ianmjones,

2
getoptsu Linux non è un'utilità GNU e il tradizionale getoptnon proviene inizialmente da BSD ma da AT&T Unix. ksh93 getopts(anche di AT&T) supporta opzioni lunghe in stile GNU.
Stephane Chazelas,

@StephaneChazelas - modificato per riflettere i tuoi commenti. Preferisco ancora il termine "GNU getopt" anche se è un termine improprio, perché questa versione segue le convenzioni GNU e generalmente si comporta come un programma GNU (ad esempio facendo uso di POSIXLY_CORRECT), mentre "getopt avanzato con Linux" suggerisce erroneamente che questa versione esiste solo su Linux.
Urban Vagabond,

1
Viene dal pacchetto util-linux, quindi è Linux solo perché quel pacchetto di software è pensato solo per Linux (che getoptpuò essere facilmente portato su altri Unice, ma molti altri software util-linuxsono specifici di Linux). Tutti i programmi non GNU che fanno uso di GNU getopt (3) comprendono $POSIX_CORRECT. Ad esempio, non diresti che aplayè GNU solo per questi motivi. Sospetto che quando FreeBSD menzioni GNU getopt, significhino l'API C GNU getopt (3).
Stephane Chazelas,

@StephaneChazelas - FreeBSD ha un messaggio di errore "Costruisci dipendenza: installa GNU getopt" che si riferisce in modo inequivocabile getoptall'utilità, non a getopt (3).
Urban Vagabond,

202

La funzione getopts incorporata in Bash può essere utilizzata per analizzare lunghe opzioni inserendo un carattere trattino seguito da due punti nell'optspec:

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

Dopo aver copiato il nome del file eseguibile = getopts_test.shnella directory di lavoro corrente , si può produrre un output simile

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

Ovviamente getopts non esegue il OPTERRcontrollo né l'analisi dell'argomento opzione per le opzioni lunghe. Il frammento di script sopra mostra come eseguire questa operazione manualmente. Il principio di base funziona anche nella shell Debian Almquist ("trattino"). Nota il caso speciale:

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

Si noti che, come sottolinea GreyCat all'indirizzo http://mywiki.wooledge.org/BashFAQ , questo trucco sfrutta un comportamento non standard della shell che consente l'argomento-opzione (ovvero il nome file in "-f nomefile") da concatenare all'opzione (come in "-ffilename"). Lo standard POSIX afferma che ci deve essere uno spazio tra loro, che nel caso di "- longoption" terminerebbe l'analisi delle opzioni e trasformerebbe tutte le longoption in argomenti non-option.


2
Una domanda: qual è la semantica di !in val="${!OPTIND}?
TomRoche,

2
@TomRoche è Sostituzione indiretta: unix.stackexchange.com/a/41293/84316
ecbrodie

2
@ecbrodie: è perché due argomenti sono stati effettivamente elaborati, a differenza di uno solo. Il primo argomento è la parola "loglevel" e il successivo è l'argomento di tale argomento. Nel frattempo, getoptsaumenta automaticamente solo OPTINDcon 1, ma nel nostro caso abbiamo bisogno che aumenti di 2, quindi lo incrementiamo di 1 manualmente, quindi lo getoptsincrementiamo di 1 automaticamente per noi.
Victor Zamanian,

3
Dato che qui ci troviamo nell'equilibrismo bash: i nomi delle variabili nude sono consentiti all'interno delle espressioni aritmetiche, non $necessario. OPTIND=$(( $OPTIND + 1 ))può essere giusto OPTIND=$(( OPTIND + 1 )). Ancora più interessante, puoi persino assegnare e aumentare le variabili all'interno di un'espressione aritmetica, quindi è possibile abbreviarlo ulteriormente : $(( ++OPTIND ))o anche (( ++OPTIND ))tener conto che ++OPTINDsarà sempre positivo, quindi non farà scattare una shell eseguita con l' -eopzione. :-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
clacke

3
Perché non --very-badavvisare?
Tom Hale,

148

Il getoptscomando integrato è ancora, AFAIK, limitato solo alle opzioni a carattere singolo.

Esiste (o getoptesisteva ) un programma esterno che avrebbe riorganizzato una serie di opzioni in modo che fosse più semplice analizzarlo. Potresti adattare quel design anche per gestire opzioni lunghe. Esempio di utilizzo:

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

# Process remaining non-option arguments
...

È possibile utilizzare uno schema simile con un getoptlongcomando.

Si noti che la debolezza fondamentale con il getoptprogramma esterno è la difficoltà di gestire gli argomenti con spazi in essi e di preservare accuratamente quegli spazi. Questo è il motivo per cui il built-in getoptsè superiore, sebbene limitato dal fatto che gestisce solo opzioni a lettera singola.


11
getopt, ad eccezione della versione GNU (che ha una diversa convenzione di chiamata), è sostanzialmente rotto. Non usarlo. Si prega di utilizzare ** getopts invece bash-hackers.org/wiki/doku.php/howto/getopts_tutorial
hendry

9
@hendry - dal tuo link: "Nota che getopts non è in grado di analizzare le opzioni lunghe in stile GNU (--myoption) o le opzioni lunghe in stile XF86 (-myoption)!"
Tom Auger,

1
Jonathan - dovresti riscrivere l'esempio da usare eval setcon le virgolette (vedi la mia risposta sotto) in modo che funzioni anche correttamente con GNU getopt (il default su Linux) e gestisca correttamente gli spazi.
Urban Vagabond,

@UrbanVagabond: non sono sicuro del motivo per cui dovrei farlo. La domanda è taggata Unix, non Linux. Sto mostrando il meccanismo tradizionale, deliberatamente, e ha problemi con gli spazi vuoti negli argomenti, ecc. Puoi dimostrare la moderna versione specifica di Linux se lo desideri, e la tua risposta lo fa. (Noto, passim, che il tuo uso ${1+"$@"}è caratteristico e in contrasto con ciò che è necessario nelle shell moderne e in particolare con qualsiasi shell che potresti trovare su Linux. Vedi Usare $ 1: + "$ @"} in / bin / sh per un discussione di tale notazione).
Jonathan Leffler il

Dovresti farlo perché eval setfa la cosa giusta sia con GNU che con BSD getopt, mentre Plain setfa solo la cosa giusta con BSD getopt. Quindi puoi anche eval setincoraggiare le persone a prendere l'abitudine di farlo. A proposito grazie, non mi rendevo conto che ${1+"$@"}non era più necessario. Devo scrivere cose che funzionino sia su Mac OS X che su Linux - tra loro costringono molta portabilità. Ho appena controllato e "$@"in effetti la cosa giusta su tutti sh, bash, ksh, e zshin Mac OS X; sicuramente anche sotto Linux.
Urban Vagabond,

78

Ecco un esempio che attualmente utilizza getopt con opzioni lunghe:

aflag=no
bflag=no
cargument=none

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

set -- $options

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

1
Dovresti riscrivere l'esempio da usare eval setcon le virgolette (vedi la mia risposta sotto) in modo che funzioni anche correttamente con GNU getopt (il default su Linux) e gestisca correttamente gli spazi.
Urban Vagabond,

2
Questo sta usando getoptmentre la domanda riguarda getoptsperò.
Niklas Berglund,

1
Sono (--, (-*e (*modelli validi? Come sono diversi da --, -*e *?
Maëlan,

1
@ Maëlan - La parentesi aperta iniziale è facoltativa, quindi (--)è identica a --)in una casestanza. È strano vedere il rientro irregolare e l'uso incoerente di quelle parentesi iniziali facoltative, ma il codice corrente della risposta mi sembra valido.
Adam Katz,

59

Le opzioni lunghe possono essere analizzate dall'integrato standard getoptscome "argomenti" per l ' -"opzione"

Questa è una shell POSIX portatile e nativa - non sono necessari programmi esterni o basismi.

Questa guida implementa le opzioni lunghe come argomenti -all'opzione, quindi --alphaè vista da getoptscome -con argomento alphaed --bravo=fooè vista come -con argomento bravo=foo. Il vero argomento può essere raccolto con una semplice sostituzione: ${OPTARG#*=}.

In questo esempio, -be -c(e le loro forme lunghe, --bravoe --charlie) hanno argomenti obbligatori. Gli argomenti alle opzioni lunghe arrivano dopo segni di uguale, ad esempio --bravo=foo(i delimitatori di spazio per le opzioni lunghe sarebbero difficili da implementare, vedi sotto).

Poiché utilizza questo getoptsbuilt-in , questa soluzione supporta un utilizzo simile cmd --bravo=foo -ac FILE(che ha combinato opzioni -ae -cinterleave lunghe opzioni con opzioni standard) mentre la maggior parte delle altre risposte qui o fanno fatica o non riescono a farlo.

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

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

Quando l'opzione è un trattino ( -), è un'opzione lunga. getoptsavrà analizzato l'opzione lunga effettiva in $OPTARG, ad esempio --bravo=fooimposta originariamente OPT='-'e OPTARG='bravo=foo'. La ifstanza imposta $OPTil contenuto di $OPTARGprima del primo segno di uguale ( bravonel nostro esempio) e quindi lo rimuove dall'inizio di $OPTARG(cedendo =fooin questo passaggio, o una stringa vuota se non c'è =). Infine, spogliamo l'argomento principale =. A questo punto, $OPTè un'opzione breve (un carattere) o un'opzione lunga (2+ caratteri).

L' caseallora corrisponde né a breve né opzioni lunghe. Per le opzioni brevi, getoptssi lamenta automaticamente delle opzioni e degli argomenti mancanti, quindi dobbiamo replicarli manualmente utilizzando la needs_argfunzione, che si chiude fatalmente quando $OPTARGè vuota. La ??*condizione corrisponderà a qualsiasi opzione lunga rimanente ( ?corrisponde a un singolo carattere e *corrisponde a zero o più, quindi ??*corrisponde a 2+ caratteri), consentendoci di emettere l'errore "Opzione illegale" prima di uscire.

(Una nota sui nomi delle variabili maiuscole: in generale, il consiglio è di riservare le variabili maiuscole per l'uso del sistema. Sto mantenendo $OPTle maiuscole per mantenerle in linea $OPTARG, ma questo rompe quella convenzione. Penso che si adatta perché questo è qualcosa che il sistema avrebbe dovuto fare e dovrebbe essere sicuro perché non ci sono standard (afaik) che utilizzano una tale variabile.)


Per lamentarti di argomenti inattesi con opzioni lunghe, imita ciò che abbiamo fatto per argomenti obbligatori: usa una funzione di supporto. Basta girare il test per lamentarsi di un argomento quando non ci si aspetta:

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

Una versione precedente di questa risposta tentava di accettare lunghe opzioni con argomenti delimitati da spazi, ma non era affidabile; getoptspotrebbe terminare prematuramente supponendo che l'argomento fosse oltre il suo ambito e che l'incremento manuale $OPTINDnon funzionasse in tutte le shell.

Ciò sarebbe realizzato utilizzando una di queste tecniche:

e poi concluso con qualcosa di simile [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))


Soluzione autonoma molto bella. Una domanda: poiché letter-cnon ha bisogno di argomenti, non sarebbe sufficiente usarlo letter-c)? il *sembra ridondante.
Philip Kearns,

1
@Arne Gli argomenti posizionali sono UX sbagliati; sono difficili da capire e gli argomenti opzionali sono piuttosto disordinati. getoptssi ferma al primo argomento posizionale poiché non è progettato per affrontarli. Ciò consente ai comandi secondari con i propri argomenti, ad esempio git diff --color, quindi interpreterei command --foo=moo bar --baz wazcome avere --fooun argomento commande --baz wazcome argomento (con opzione) al barcomando secondario. Questo può essere fatto con il codice sopra. Respingo --bravo -blahperché --bravorichiede un argomento e non è chiaro che -blahnon sia un'altra opzione.
Adam Katz,

1
Non sono d'accordo sull'UX: gli argomenti posizionali sono utili e facili, purché si limiti il ​​loro numero (al massimo 2 o 1 più N-dello-stesso-tipo). Dovrebbe essere possibile separarli con argomenti di parole chiave, perché gli utenti possono quindi creare un comando passo dopo passo (ad esempio ls abc -la).
Arne Babenhauserheide,

1
@AdamKatz: ho scritto un piccolo articolo con questo: draketo.de/english/free-software/shell-argument-parsing - include la lettura ripetuta degli argomenti rimanenti per catturare le opzioni finali.
Arne Babenhauserheide,

1
@ArneBabenhauserheide: ho aggiornato questa risposta per supportare argomenti delimitati da spazi. Poiché richiede evalnella shell POSIX, è elencato sotto il resto della risposta.
Adam Katz,

33

Dai un'occhiata a shFlags che è una libreria di shell portatile (che significa: sh, bash, dash, ksh, zsh su Linux, Solaris, ecc.).

Rende l'aggiunta di nuovi flag semplice come l'aggiunta di una riga allo script e fornisce una funzione di utilizzo generata automaticamente.

Ecco un semplice Hello, world!utilizzo di shFlag :

#!/bin/sh

# source shflags from current directory
. ./shflags

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

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

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

Per i sistemi operativi con getopt avanzato che supporta opzioni lunghe (ad es. Linux), puoi fare:

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

Per il resto, è necessario utilizzare l'opzione breve:

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

Aggiungere una nuova bandiera è semplice come aggiungere una nuova DEFINE_ call.


2
Questo è fantastico ma purtroppo il mio getopt (OS X) non supporta gli spazi negli argomenti: / mi chiedo se ci sia un'alternativa.
Alastair Stuart

@AlastairStuart - esiste davvero un'alternativa su OS X. Usa MacPorts per installare GNU getopt (di solito verrà installato in / opt / local / bin / getopt).
Urban Vagabond,

3
@UrbanVagabond - l'installazione di strumenti di default non di sistema non è purtroppo un requisito accettabile per uno strumento sufficientemente portatile.
Alastair Stuart,

@AlastairStuart - vedi la mia risposta per una soluzione portatile che utilizza getopts incorporato anziché GNU getopt. È lo stesso dell'utilizzo di base getopts ma con un'iterazione extra per le opzioni lunghe.
Adam Katz,

31

Utilizzo getoptscon opzioni / argomenti brevi / lunghi


Funziona con tutte le combinazioni, ad esempio:

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

Alcune dichiarazioni per questo esempio

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

Come sarebbe la funzione di utilizzo

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

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

getops con flag long / short e argomenti lunghi

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

Produzione

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

Combinando quanto sopra in uno script coerente

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

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

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

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

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

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

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

Questo non funziona con più di un argomento lungo (-). Sembra leggere solo il primo per me.
Sinaesthetic,

@Sinaesthetic - Sì, stavo giocando con l' evalapproccio per argomenti distanziati su lunghe opzioni e l'ho trovato inaffidabile con alcune shell (anche se mi aspetto che funzionerà con bash, nel qual caso non è necessario utilizzare eval). Vedi la mia risposta per come accettare lunghi argomenti con opzioni =e i miei noti tentativi di usare lo spazio. La mia soluzione non effettua chiamate esterne mentre questa viene utilizzata cutalcune volte.
Adam Katz,

24

Un altro modo...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done

1
Non è necessario uno spazio in ogni $argsriassegnazione? Questo potrebbe anche essere fatto senza bashismi, ma questo codice perderà spazi in opzioni e argomenti (non credo che il $delimtrucco funzionerà). È invece possibile eseguire set all'interno del forciclo se si è abbastanza attenti a svuotarlo sul solo la prima iterazione. Ecco una versione più sicura senza basismi.
Adam Katz,

18

Ho risolto in questo modo:

# A string with command options
options=$@

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

# Loop index
index=0

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

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

exit;

Sono stupido o qualcosa del genere? getopte getoptssono così confusi.


1
Questo sembra funzionare per me, non so quale sia il problema con questo metodo, ma sembra semplice, quindi ci deve essere un motivo per cui tutti gli altri non lo stanno usando.
Billy Moon,

1
@Billy Sì, questo è semplice perché non uso alcuno script per gestire i miei parametri e così via. Fondamentalmente, converto la stringa di argomenti ($ @) in un array e lo eseguo in loop. Nel loop, il valore corrente sarà la chiave e quello successivo sarà il valore. Semplice come quella.

1
@Theodore Sono contento che ti sia stato utile! Anche per me è stato un dolore. Se sei interessato, puoi vederne un esempio in azione qui: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh

2
Sicuramente il modo più semplice che abbia mai visto. L'ho cambiato un po 'come usare i = $ (($ i + 1)) invece di expr ma il concetto è a tenuta stagna.
Thomas Dignan,

6
Non sei affatto stupido, ma potresti non avere una caratteristica: getopt (s) può riconoscere le opzioni che sono miste (es: -ltro -lt -rcosì come -l -t -r). Inoltre fornisce una gestione degli errori e un modo semplice per spostare i parametri trattati una volta terminato il trattamento delle opzioni.
Olivier Dulac,

14

Nel caso in cui non si desideri la getoptdipendenza, è possibile farlo:

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

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

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

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

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

Naturalmente, quindi non puoi utilizzare le opzioni di stile lungo con un trattino. E se vuoi aggiungere versioni abbreviate (es. --Verbos invece di --verbose), devi aggiungerle manualmente.

Ma se stai cercando di ottenere getoptsfunzionalità insieme a lunghe opzioni, questo è un modo semplice per farlo.

Ho anche messo questo snippet in sintesi .


Questo sembra funzionare solo con una lunga opzione alla volta, ma ha soddisfatto le mie esigenze. Grazie!
Kingjeffrey,

Nel caso speciale --)sembra shift ;mancare. Al momento --rimarrà come primo argomento non opzionale.
Giorno

Penso che questa sia in realtà la risposta migliore, anche se, come sottolinea dgw, l' --opzione ha bisogno di essere inclusa shift. Dico che questo è meglio perché le alternative sono versioni dipendenti dalla piattaforma getopto getopts_longoppure devi forzare le opzioni brevi da utilizzare solo all'inizio del comando (ovvero getopts, dopo utilizzi quindi elabora opzioni lunghe), mentre ciò dà qualsiasi ordine e controllo completo.
Haravikk,

Questa risposta mi fa chiedermi perché abbiamo un filo di dozzine di risposte per fare il lavoro che non può essere fatto con nient'altro che questa soluzione assolutamente chiara e semplice , e se c'è qualche motivo per il miliardo di casi di utilizzo di getopt diversi dal provare se stessi.
Florian Heigl,

11

Il built-in getoptsnon può farlo. Esiste un programma getopt esterno (1) che può farlo, ma lo ottieni solo su Linux dal pacchetto util-linux . Viene fornito con uno script di esempio getopt-parse.bash .

C'è anche una getopts_longscritta come funzione shell.


3
È getoptstato incluso in FreeBSD versione 1.0 nel 1993, e da allora fa parte di FreeBSD. Come tale, è stato adottato da FreeBSD 4.x per l'inclusione nel progetto Darwin di Apple. A partire da OS X 10.6.8, la pagina man inclusa da Apple rimane un duplicato esatto della pagina man di FreeBSD. Quindi sì, è incluso in OS X e gobs di altri sistemi operativi oltre a Linux. -1 su questa risposta per la disinformazione.
Ghoti,

8
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

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

exit

.

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit

2
Una spiegazione sarebbe buona. Il primo script accetta opzioni brevi solo mentre il secondo script ha un bug nell'analisi dell'argomento delle opzioni lunghe; la sua variabile dovrebbe essere "${1:0:1}"per l'argomento n. 1, sottostringa all'indice 0, lunghezza 1. Ciò non consente di mescolare opzioni corte e lunghe.
Adam Katz,

7

In ksh93, getoptssupporta nomi lunghi ...

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

Almeno così hanno detto i tutorial che ho trovato. Provalo e vedi.


4
Questo è il getopts incorporato di ksh93. Oltre a questa sintassi, ha anche una sintassi più complicata che consente anche opzioni lunghe senza un equivalente corto, e altro ancora.
jilles,

2
Una risposta ragionevole L'OP non ha specificato QUALE shell.
ghoti,

6

Scrivo solo script di shell di tanto in tanto e fallisco, quindi ogni feedback è apprezzato.

Usando la strategia proposta da @Arvid Requate, abbiamo notato alcuni errori dell'utente. Un utente che dimentica di includere un valore avrà accidentalmente il nome dell'opzione successiva trattato come un valore:

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

farà apparire il valore di "loglevel" come "--toc = TRUE". Questo può essere evitato.

Ho adattato alcune idee sul controllo dell'errore utente per l'interfaccia della riga di comando da http://mwiki.wooledge.org/BashFAQ/035 discussione sull'analisi manuale. Ho inserito il controllo degli errori nella gestione di entrambi gli argomenti "-" e "-".

Poi ho iniziato a armeggiare con la sintassi, quindi qualsiasi errore qui è strettamente colpa mia, non degli autori originali.

Il mio approccio aiuta gli utenti che preferiscono entrare a lungo con o senza il segno uguale. Cioè, dovrebbe avere la stessa risposta a "--loglevel 9" come "--loglevel = 9". Nel metodo - / space, non è possibile sapere con certezza se l'utente dimentica un argomento, quindi è necessario indovinare.

  1. se l'utente ha il formato di segno lungo / uguale (--opt =), quindi uno spazio dopo = genera un errore perché non è stato fornito un argomento.
  2. se l'utente ha argomenti long / space (--opt), questo script provoca un errore se non segue alcun argomento (fine del comando) o se l'argomento inizia con un trattino)

Nel caso tu stia iniziando su questo, c'è una differenza interessante tra i formati "--opt = value" e "--opt value". Con lo stesso segno, l'argomento della riga di comando viene visto come "opt = value" e il lavoro da gestire che consiste nell'analisi delle stringhe, da separare in "=". Al contrario, con "--opt value", il nome dell'argomento è "opt" e abbiamo la sfida di ottenere il valore successivo fornito nella riga di comando. Ecco dove @Arvid Requate ha utilizzato $ {! OPTIND}, il riferimento indiretto. Continuo a non capirlo, beh, a tutti, e i commenti in BashFAQ sembrano mettere in guardia contro quello stile ( http://mywiki.wooledge.org/BashFAQ/006 ). A proposito, non credo che i precedenti commenti del poster sull'importanza di OPTIND = $ (($ OPTIND + 1)) siano corretti. Intendo dire

Nella versione più recente di questo script, flag -v indica la stampa VERBOSE.

Salvalo in un file chiamato "cli-5.sh", rendilo eseguibile e ognuno di questi funzionerà o fallirà nel modo desiderato

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

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

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

Ecco un esempio di output del controllo degli errori su intpu utente

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

Dovresti considerare di attivare -v, perché stampa gli interni di OPTIND e OPTARG

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

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

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


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

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

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


VERBOSE=0
loglevel=0
toc="TRUE"

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

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

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

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



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"

OPTIND=$(( $OPTIND + 1 )): è necessario ogni volta che si "divorano" il parametro di OPTIND (per esempio: quando usato --toc value : il valore è nel numero di parametro $ OPTIND. Una volta recuperato per il valore di toc, si dovrebbe dire a getopts che il prossimo parametro da analizzare non è valore, ma quello dopo di esso (quindi: .e il OPTIND=$(( $OPTIND + 1 )) tuo script (così come lo script a cui ti riferisci) mancano, dopo il fatto: shift $(( $OPTIND -1 ))(poiché getopts è uscito dopo aver analizzato i parametri 1 su OPTIND-1, devi spostarli fuori così $@sono ora tutti i parametri "non-opzioni" rimanenti
Olivier Dulac,

oh, mentre ti sposti, "sposta" i parametri sotto getopts, quindi OPTIND punta sempre la cosa giusta ... ma la trovo molto confusa. Credo (non posso testare il tuo script in questo momento) che hai ancora bisogno dello spostamento $ (($ OPTIND - 1)) dopo il ciclo getopts while, quindi $ 1 ora non punta all'originale $ 1 (un'opzione) ma al primo dei restanti argomenti (quelli che seguono tutte le opzioni e i loro valori). es: myrm -foo -bar = baz thisarg thenthisone thenanother
Olivier Dulac

5

Inventare l'ennesima versione della ruota ...

Questa funzione è una sostituzione della shell bourne semplice (eventualmente) compatibile POSIX per GNU getopt. Supporta opzioni short / long che possono accettare argomenti obbligatori / optional / no, e il modo in cui le opzioni sono specificate è quasi identico a GNU getopt, quindi la conversione è banale.

Naturalmente questo è ancora un grosso pezzo di codice da inserire in uno script, ma è circa la metà delle righe della nota funzione di shell getopt_long e potrebbe essere preferibile nei casi in cui si desidera semplicemente sostituire gli usi getopt GNU esistenti.

Questo è un codice piuttosto nuovo, quindi YMMV (e per favore fatemi sapere se questo non è effettivamente compatibile con POSIX per qualsiasi motivo - la portabilità era l'intenzione fin dall'inizio, ma non ho un utile ambiente di test POSIX).

Segue l'utilizzo di codice ed esempio:

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

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

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

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

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

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

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

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

Esempio di utilizzo:

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

4

La risposta accettata fa un ottimo lavoro nel sottolineare tutte le carenze di Bash integrato getopts. La risposta termina con:

Quindi, mentre è possibile scrivere più codice per ovviare alla mancanza di supporto per le opzioni lunghe, questo è molto più lavoro e parzialmente vanifica lo scopo dell'utilizzo di un parser getopt per semplificare il codice.

E anche se concordo in linea di principio con questa affermazione, ritengo che il numero di volte in cui tutti abbiamo implementato questa funzione in vari script giustifichi un certo sforzo per creare una soluzione "standardizzata" e ben testata.

Come tale, ho "aggiornato" bash integrato getoptsimplementando getopts_longin puro bash, senza dipendenze esterne. L'uso della funzione è compatibile al 100% con quello integrato getopts.

Includendo getopts_long(che è ospitato su GitHub ) in uno script, la risposta alla domanda originale può essere implementata semplicemente come:

source "${PATH_TO}/getopts_long.bash"

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

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

3

Non ho ancora abbastanza rappresentante per commentare o votare la sua soluzione, ma la risposta di SME ha funzionato molto bene per me. L'unico problema in cui mi sono imbattuto è che gli argomenti finiscono racchiusi tra virgolette singole (quindi le ho eliminate).

Ho anche aggiunto alcuni esempi di utilizzo e testo di AIUTO. Includerò qui la mia versione leggermente estesa:

#!/bin/bash

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

    Accepts the following forms:\n\n

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

aflag=false
bflag=false
cargument=""

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

set -- $options

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

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

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

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

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

done

3

Qui puoi trovare alcuni approcci diversi per l'analisi delle opzioni complesse in bash: http://mywiki.wooledge.org/ComplexOptionParsing

Ho creato il seguente, e penso che sia buono, perché è un codice minimo e funzionano sia le opzioni long che short. Un'opzione lunga può anche avere più argomenti con questo approccio.

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

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

# End of file

2

Ho lavorato su questo argomento per un bel po 'di tempo ... e ho creato la mia biblioteca che dovrai procurarti nella tua sceneggiatura principale. Vedi libopt4shell e cd2mpc per un esempio. Spero che sia d'aiuto !


2

Una soluzione migliorata:

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

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

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

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

2

Forse è più semplice usare ksh, solo per la parte getopts, se sono necessarie lunghe opzioni da linea di comando, poiché può essere più facile farlo lì.

# Working Getopts Long => KSH

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

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done

+1 - Nota che questo è limitato a ksh93 - dal progetto AST open source (AT&T Research).
Henk Langeveld,

2

Volevo qualcosa senza dipendenze esterne, con un supporto bash rigoroso (-u), e ne avevo bisogno per funzionare anche con le versioni bash precedenti. Questo gestisce vari tipi di parametri:

  • bool corto (-h)
  • opzioni brevi (-i "image.jpg")
  • bool lunghi (--help)
  • uguale a opzioni (--file = "nomefile.ext")
  • opzioni di spazio (--file "nomefile.ext")
  • bool concatinati (-hvm)

Basta inserire quanto segue nella parte superiore dello script:

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

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

  local param_pair
  local key
  local value
  local shift_count

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

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

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

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

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

E usalo così:

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

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

# Get the params from arguments provided
_get_params $*

1

Per rimanere compatibile multipiattaforma ed evitare la dipendenza da eseguibili esterni, ho portato il codice da un'altra lingua.

Lo trovo molto facile da usare, ecco un esempio:

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

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

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

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

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

Il BASH richiesto è un po 'più lungo di quanto potrebbe essere, ma volevo evitare di fare affidamento sugli array associativi di BASH 4. Puoi anche scaricare questo direttamente da http://nt4.com/bash/argparser.inc.sh

#!/usr/bin/env bash

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

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

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

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

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

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

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


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

function decho
{
    :
}

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

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

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

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

function ArgParser::parse
{

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

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

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

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

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

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

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

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

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

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

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

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

        description="${EXPLODED[4]}"

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

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

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

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

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

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

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

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

}

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

1

Se tutte le opzioni lunghe hanno caratteri univoci e corrispondenti, primi caratteri come opzioni brevi, quindi ad esempio

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

Equivale a

./slamm -c 23 -p test -q

Puoi usarlo prima di getopts per riscrivere $ args:

# change long options to short options

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

# reset the incoming args
eval set -- $args

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

Grazie per mtvee per l'ispirazione ;-)


Non capisco il significato di eval qui
user.friendly

1

se semplicemente è così che vuoi chiamare lo script

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

allora puoi seguire questo modo più semplice per raggiungerlo con l'aiuto di getopt e --longoptions

prova questo, spero che sia utile

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



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


echo $opts

eval set --$opts

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

0

getopts "potrebbe essere usato" per analizzare le opzioni lunghe purché non ti aspetti che abbiano argomenti ...

Ecco come:

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

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

Se si tenta di utilizzare OPTIND per ottenere un parametro per l'opzione lunga, getopts lo considererà come il primo parametro posizionale non opzionale e interromperà l'analisi di altri parametri. In tal caso, sarà meglio gestirlo manualmente con una semplice dichiarazione del caso.

Questo funzionerà "sempre":

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

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


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

Anche se non è flessibile come getopts e devi fare gran parte dell'errore controllando te stesso il codice all'interno delle istanze del caso ...

Ma è un'opzione.


Ma le opzioni lunghe spesso prevedono argomenti. E potresti fare di più con il - per farlo funzionare anche se è una specie di hack. Alla fine si potrebbe sostenere che se non lo supporta in modo nativo, ogni modo di implementarlo è una sorta di hack ma tuttavia è possibile estendere anche il - . E sì shift è molto utile, ma ovviamente se si aspetta un argomento potrebbe finire per dire che l'argomento successivo (se non specificato dall'utente) fa parte dell'argomento previsto.
Pryftan,

sì, questo è un poc per nomi di argomenti lunghi senza argomenti, per differenziare tra i due è necessario un qualche tipo di configurazione, come getops. E per quanto riguarda il turno, puoi sempre "rimetterlo" con set. In ogni caso deve essere configurabile se si prevede o meno un parametro. Potresti anche usare un po 'di magia per farlo, ma poi costringerai gli utenti a usarlo - per segnalare che la magia si interrompe e iniziano i parametri posizionali, il che è peggio imho.
estani,

Giusto. È più che ragionevole. Tbh non ricordo nemmeno cosa stavo succedendo e mi ero completamente dimenticato di questa domanda stessa. Ricordo solo vagamente come l'ho trovato anche. Saluti. Oh, e hai un +1 per l'idea. Hai superato lo sforzo e hai anche chiarito a cosa stavi arrivando. Rispetto le persone che fanno lo sforzo di dare idee agli altri, ecc.
Pryftan,

0

builtin getopts analizza solo opzioni brevi (tranne in ksh93), ma puoi comunque aggiungere alcune righe di scripting per fare in modo che getopts gestisca opzioni lunghe.

Ecco una parte del codice disponibile in http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

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

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

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

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

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

Ecco un test:

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

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

Altrimenti nel recente Korn Shell ksh93, getoptspuò naturalmente analizzare lunghe opzioni e persino visualizzare una pagina man allo stesso modo. (Vedi http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )


0

Th integrato in OS X (BSD) getopt non supporta le opzioni lunghe, ma la versione GNU fa: brew install gnu-getopt. Poi, qualcosa di simile a: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.


0

EasyOptions gestisce opzioni brevi e lunghe:

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

source easyoptions || exit

if test -n "${verbose}"; then
    echo "log file: ${logfile}"
    echo "arguments: ${arguments[@]}"
fi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.