Come posso analizzare argomenti opzionali in uno script bash se non viene dato alcun ordine?


13

Sono confuso su come includere argomenti / flag opzionali quando si scrive uno script bash per il seguente programma:

Il programma richiede due argomenti:

run_program --flag1 <value> --flag2 <value>

Tuttavia, ci sono diversi flag opzionali:

run_program --flag1 <value> --flag2 <value> --optflag1 <value> --optflag2 <value> --optflag3 <value> --optflag4 <value> --optflag5 <value> 

Vorrei eseguire lo script bash in modo tale da accettare gli argomenti dell'utente. Se gli utenti inseriscono solo due argomenti in ordine, allora sarebbe:

#!/bin/sh

run_program --flag1 $1 --flag2 $2

E se fosse incluso uno degli argomenti opzionali? Penserei che sarebbe

if [ --optflag1 "$3" ]; then
    run_program --flag1 $1 --flag2 $2 --optflag1 $3
fi

Ma cosa succede se vengono dati $ 4 ma non $ 3?


getoptsè quello che vuoi. Senza questo, è possibile utilizzare un ciclo con un'istruzione switch per rilevare ogni flag, facoltativo o meno.
Orion,


@orion Con getopts, dovrei specificare ogni combinazione di argomenti? 3 e 4, 3 e 5, 3 e 4 e 5, ecc.?
ShanZhengYang,

No, le imposta semplicemente se le ottieni, altrimenti segnala che non è stato trovato, quindi in pratica "prendi" ciascuna opzione, in qualunque ordine, e le specifichi in qualsiasi ordine, se non del tutto. Ma basta leggere la pagina man di bash, è tutto lì.
Orion,

@orion Mi dispiace, ma ancora non capisco bene getopts. Diciamo che costringo gli utenti a eseguire lo script con tutti gli argomenti: run_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSEche esegue il programma come program --flag1 VAL --flag2 VAL. Se eseguito run_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE, il programma verrebbe eseguito come program --flag1 VAL --flag2 VAL --optflag2 10. Come puoi ottenere questo comportamento getopts?
ShanZhengYang,

Risposte:


25

Questo articolo mostra due modi diversi - shifte getopts(e discute i vantaggi e gli svantaggi dei due approcci).

Con il shifttuo script guarda $1, decide quale azione intraprendere, quindi esegue shift, passa $2a $1, $3a $2, ecc.

Per esempio:

while :; do
    case $1 in
        -a|--flag1) flag1="SET"            
        ;;
        -b|--flag2) flag2="SET"            
        ;;
        -c|--optflag1) optflag1="SET"            
        ;;
        -d|--optflag2) optflag2="SET"            
        ;;
        -e|--optflag3) optflag3="SET"            
        ;;
        *) break
    esac
    shift
done

Con getoptste definisci le opzioni (brevi) whilenell'espressione:

while getopts abcde opt; do
    case $opt in
        a) flag1="SET"
        ;;
        b) flag2="SET"
        ;;
        c) optflag1="SET"
        ;;
        d) optflag2="SET"
        ;;
        e) optflag3="SET"
        ;;
    esac
done

Ovviamente, questi sono solo frammenti di codice e ho tralasciato la convalida - controllando che siano impostati gli arg1 obbligatori flag1 e flag2, ecc.

L'approccio che usi è in una certa misura una questione di gusti: quanto portatile vuoi che sia il tuo script, se puoi vivere solo con opzioni brevi (POSIX) o se vuoi opzioni lunghe (GNU), ecc.


Di solito lo faccio while (( $# ))invece while :;e spesso esco con un errore nel *caso
Jasen,

questo risponde al titolo ma non alla domanda.
Jasen,

@Jasen Ho guardato quest'ultima notte e non ho potuto capire perché. Questa mattina è molto più chiaro (e ora ho visto anche la risposta di Orion). Oggi cancellerò questa risposta (volevo prima riconoscere il tuo commento e questo sembrava il modo più semplice per farlo).
John N,

nessun problema, è ancora una buona risposta, solo la domanda l'ha schivata.
Jasen,

1
NB: Nel caso della set -o nounsetprima soluzione si spegnerà se non viene fornito alcun parametro. Correzione:case ${1:-} in
Raffaello,

3

usa un array.

#!/bin/bash

args=( --flag1 "$1" --flag2 "$2" )
[  "x$3" = xFALSE ] ||  args+=( --optflag1 "$3" )
[  "x$4" = xFALSE ] ||  args+=( --optflag2 "$4" )
[  "x$5" = xFALSE ] ||  args+=( --optflag3 "$5" )
[  "x$6" = xFALSE ] ||  args+=( --optflag4 "$6" )
[  "x$7" = xFALSE ] ||  args+=( --optflag5 "$7" )

program_name "${args[@]}"

questo gestirà correttamente gli argomenti con spazi.

[modifica] Stavo usando la sintassi approssimativamente eqivalente args=( "${args[@]}" --optflag1 "$3" )ma G-Man mi ha suggerito un modo migliore.


1
Puoi semplificare un po 'dicendo args+=( --optflag1 "$3" ). Potresti voler vedere la mia risposta al nostro lavoro di riferimento, implicazioni per la sicurezza di non riuscire a citare una variabile nelle shell bash / POSIX , dove discuto questa tecnica (di costruire una riga di comando in un array aggiungendo condizionalmente argomenti opzionali).
G-Man dice "Reinstate Monica"

@ G-Man Grazie, non l'avevo visto nella documentazione, capendo di [@]avere uno strumento sufficiente per risolvere il mio problema.
Jasen,

1

In uno script shell, gli argomenti sono "$ 1", "$ 2", "$ 3", ecc. Il numero di argomenti è $ #.
Se il tuo script non riconosce le opzioni, puoi tralasciare il rilevamento delle opzioni e trattare tutti gli argomenti come operandi.
Per riconoscere le opzioni usa il getopts incorporato


0

Se le opzioni di input sono posizionali (sai in quali luoghi si trovano) e non specificate con flag, allora quello che vuoi è solo costruire la riga di comando. Basta preparare gli argomenti del comando per tutti loro:

FLAG1="--flag1 $1"
FLAG2="--flag2 $2"
OPTFLAG1=""
OPTFLAG2=""
OPTFLAG3=""
if [ xFALSE != x"$3" ]; then
   OPTFLAG1="--optflag1 $3"
fi
if [ xFALSE != x"$4" ]; then
   OPTFLAG2="--optflag2 $4"
fi
#the same for other flags

#now just run the program
runprogram $FLAG1 $FLAG2 $OPTFLAG1 $OPTFLAG2 $OPTFLAG3

Se i parametri non vengono specificati, le stringhe corrispondenti sono vuote e non si espandono in nulla. Nota che nell'ultima riga non ci sono virgolette. Questo perché vuoi che la shell divida i parametri in parole (per dare --flag1e $1come argomenti separati al tuo programma). Naturalmente, questo andrà storto se i parametri originali contengono spazi. Se sei quello che esegue questo, allora puoi lasciarlo, ma se si tratta di uno script generale, può avere un comportamento imprevisto se l'utente immette qualcosa con spazi. Per gestirlo, dovrai rendere il codice un po 'più brutto.

Il xprefisso nel []test è presente nel caso $3o $4è vuoto. In tal caso, bash si espanderebbe [ FALSE != $3 ]in [ FALSE != ]quale è un errore di sintassi, quindi un altro personaggio arbitrario è lì per proteggerlo. Questo è un modo molto comune, lo vedrai in molti script.

Ho impostato OPTFLAG1e il resto su ""all'inizio solo per essere sicuri (nel caso in cui fossero stati impostati su qualcosa prima), ma se non sono stati effettivamente dichiarati nell'ambiente, non è necessario farlo rigorosamente.

Un paio di osservazioni aggiuntive:

  • In realtà potresti semplicemente ricevere i parametri allo stesso modo di runprogram: con i flag. Ecco di cosa John Nsta parlando. Ecco dove getoptsdiventa utile.
  • L'uso di FALSE a tale scopo è un po 'fuori standard e piuttosto lungo. Di solito, si usa un singolo carattere (possibilmente -) per segnalare un valore vuoto, o semplicemente si passa una stringa vuota, come ""se non fosse altrimenti un valore valido del parametro. La stringa vuota rende anche il test più breve, basta usare if [ -n "$3" ].
  • Se esiste un solo flag opzionale o se sono dipendenti, quindi non puoi mai avere OPTFLAG2, ma non OPTFLAG1, puoi semplicemente saltare quelli che non vuoi impostare. Se lo usi ""per parametri vuoti, come suggerito sopra, puoi comunque saltare tutti gli svuotamenti finali.

Questo metodo è praticamente il modo in cui le opzioni vengono passate ai compilatori in Makefile.

E ancora: se gli input possono contenere spazi, diventa brutto.


No, non vuoi che la shell divida i parametri in parole. Dovresti sempre citare tutti i riferimenti alle variabili della shell a meno che tu non abbia una buona ragione per non farlo e sei sicuro di sapere cosa stai facendo. Vedi Implicazioni sulla sicurezza della mancata citazione di una variabile nelle shell bash / POSIX , nel caso in cui tu non ne abbia familiarità, e scorri verso il basso fino alla mia risposta , dove affronro esattamente questo problema (con la stessa tecnica che Jasen usa ).
G-Man dice "Reinstate Monica"
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.