bash come rimuovere le opzioni dai parametri dopo l'elaborazione


9

Ricordo di aver visto da qualche parte uno bashscript che utilizzava casee shiftdi scorrere l'elenco dei parametri posizionali, analizzare i flag e le opzioni con argomenti quando li incontra e li rimuove dopo l'analisi per lasciare solo gli argomenti nudi, che vengono successivamente elaborati dal resto del script.

Ad esempio, analizzando la riga di comando cp -R file1 -t /mybackup file2 -f, dovrebbe prima scorrere i parametri, riconoscere che l'utente ha richiesto di scendere nelle directory da -R, specificare la destinazione da -t /mybackupe forzare la copia da -f, e rimuovere quelli dall'elenco dei parametri, lasciando il programma da elaborare file1 file2come gli argomenti rimanenti.

Ma non riesco a ricordare / scoprire qualunque sceneggiatura ho visto ogni volta. Vorrei solo essere in grado di farlo. Ho cercato su Google vari siti e ho aggiunto un elenco di pagine pertinenti che ho esaminato.

Una domanda su questo sito Web ha chiesto specificamente "opzioni indipendenti dall'ordine", ma sia la risposta singola che la risposta alla domanda a cui è stata fornita non considerano casi come quelli sopra in cui le opzioni sono mescolate con argomenti normali, che presumo fossero le motivo per cui la persona menziona specificamente le opzioni indipendenti dall'ordine .

Dato che bashil built-in getoptssembra fermarsi al primo argomento non opzionale, non sembra essere una soluzione sufficiente. Questo è il motivo per cui la pagina di Wooledge BashFAQ (vedi sotto) spiega come riorganizzare gli argomenti. Ma vorrei evitare di creare più array nel caso in cui l'elenco degli argomenti sia piuttosto lungo.

Dal momento shiftche non supporta la rimozione di singoli argomenti dal centro dell'elenco dei parametri, non sono sicuro di quale sia un modo semplice per implementare ciò che sto chiedendo.

Mi piacerebbe sapere se qualcuno ha qualche soluzione per rimuovere gli argomenti dal centro dell'elenco dei parametri senza creare un array completamente nuovo.

Pagine che ho già visto:


1
Una volta che ho bisogno di qualcosa di complicato, passo da bash a Perl.
Choroba,

Risposte:


9

POSIXly, l'analisi delle opzioni dovrebbe fermarsi al --primo argomento non-option (o non-option-argomento) a seconda di quale si verifichi per primo. Quindi dentro

cp -R file1 -t /mybackup file2 -f

quella a file1, quindi cpdovrebbero ricorsivamente copiare tutti file1, -t, /mybackupe file2nella -fdirectory.

GNU getopt(3)comunque (che GNU cpusa per analizzare le opzioni (e qui stai usando GNU cppoiché stai usando l' -topzione specifica GNU )), a meno che non $POSIXLY_CORRECTsia impostata la variabile d'ambiente, accetta le opzioni dopo gli argomenti. Quindi è effettivamente equivalente all'analisi dello stile delle opzioni POSIX:

cp -R -t /mybackup -f -- file1 file2

La getoptsshell integrata, anche nella shell GNU ( bash), gestisce solo lo stile POSIX. Inoltre non supporta opzioni lunghe o opzioni con argomenti opzionali.

Se vuoi analizzare le opzioni nello stesso modo di GNU cp, dovrai usare l' getopt(3)API GNU . Per questo, se su Linux, puoi usare l' getoptutilità avanzata da util-linux( quella versione avanzata del getoptcomando è stata anche portata su alcuni altri Unices come FreeBSD ).

Ciò getoptriorganizzerà le opzioni in modo canonico che ti consentirà di analizzarlo semplicemente con un while/caseloop.

$ getopt -n "$0" -o t:Rf -- -Rf file1 -t/mybackup file2
 -R -f -t '/mybackup' -- 'file1' 'file2'

In genere lo useresti come:

parsed_options=$(
  getopt -n "$0" -o t:Rf -- "$@"
) || exit
eval "set -- $parsed_options"
while [ "$#" -gt 0 ]; do
  case $1 in
    (-[Rf]) shift;;
    (-t) shift 2;;
    (--) shift; break;;
    (*) exit 1 # should never be reached.
  esac
done
echo "Now, the arguments are $*"

Si noti inoltre che getoptanalizzerà le opzioni nello stesso modo di GNU cp. In particolare, supporta le opzioni lunghe (e inserendole abbreviate) e onora le $POSIXLY_CORRECTvariabili di ambiente (che quando impostate disabilita il supporto per le opzioni dopo gli argomenti) allo stesso modo di GNU cp.

Nota che usare gdb e stampare gli argomenti che getopt_long()riceve può aiutare a costruire i parametri per getopt(1):

(gdb) bt
#0  getopt_long (argc=2, argv=0x7fffffffdae8, options=0x4171a6 "abdfHilLnprst:uvxPRS:T", long_options=0x417c20, opt_index=0x0) at getopt1.c:64
(gdb) set print pretty on
(gdb) p *long_options@40
$10 = {{
    name = 0x4171fb "archive",
    has_arg = 0,
    flag = 0x0,
    val = 97
  }, {
    name = 0x417203 "attributes-only",
[...]

Quindi puoi usare getoptcome:

getopt -n cp -o abdfHilLnprst:uvxPRS:T -l archive... -- "$@"

Ricorda che cpl'elenco delle opzioni supportate di GNU può cambiare da una versione all'altra e che getoptnon sarà in grado di verificare se, ad esempio, passi un valore legale --sparseall'opzione.


@tutti: grazie per le tue risposte. @Stephane: c'è un motivo per cui stai usando while [ "$#" -gt 0 ]io solo while (($#))? È solo per evitare un bashismo?
Jamadagni,

Sì, anche se (($#))è più un kshism .
Stéphane Chazelas,

1

Quindi ogni volta che getoptselabora un argomento non si aspetta che imposta la variabile di shell $OPTINDsul numero successivo nell'elenco degli argomenti che dovrebbe elaborare e restituisce $OPTINDun valore diverso da 0. Se è impostato su un valore 1, getoptsviene specificato POSIX per accettare un nuovo elenco di argomenti. Quindi, questo solo guarda il getoptsritorno, salva incrementi di un contatore più $OPTINDper qualsiasi ritorno fallito, sposta gli argomenti falliti e reimposta $OPTINDogni tentativo fallito. Puoi usarlo come opts "$@"- anche se vorresti personalizzare il caseloop oppure salvarlo in una variabile e cambiare quella sezione in eval $case.

opts() while getopts :Rt:f opt || {
             until command shift "$OPTIND" || return 0
                   args=$args' "${'"$((a=${a:-0}+$OPTIND))"'}"'
                   [ -n "${1#"${1#-}"}" ]
             do OPTIND=1; done 2>/dev/null; continue
};     do case "$opt"  in
             R) Rflag=1      ;;
             t) tflag=1      ;
                targ=$OPTARG ;;
             f) fflag=1      ;;
       esac; done

Durante l'esecuzione imposta $argssu ogni argomento che getoptsnon ha gestito ... quindi ...

set -- -R file1 -t /mybackup file2 -f
args= opts "$@"; eval "set -- $args"
printf %s\\n "$args"
printf %s\\n "$@"         
printf %s\\n "$Rflag" "$tflag" "$fflag" "$targ"

PRODUZIONE

 "${2}" "${5}"
file1
file2
1
1
1
/mybackup

Questo funziona in bash, dash, zsh, ksh93, mksh... beh, ho smesso provando in quel punto. In ogni shell ha anche ottenuto $[Rtf]flage $targ. Il punto è che sono getoptsrimasti tutti i numeri degli argomenti che non volevano elaborare.

Anche la modifica dello stile delle opzioni non ha fatto alcuna differenza. Ha funzionato come -Rf -t/mybackupo -R -f -t /mybackup. Ha funzionato in mezzo alla lista, alla fine della lista, o in testa alla lista ...

Tuttavia, il modo migliore è quello di attaccare un --fine opzioni sul tuo elenco arg e poi farlo shift "$(($OPTIND-1))"alla fine di una getoptscorsa di elaborazione. In questo modo rimuovi tutti i parametri elaborati e mantieni la coda dell'elenco degli argomenti.

Una cosa che mi piace fare è tradurre le opzioni lunghe in brevi - e lo faccio in un modo molto simile, motivo per cui questa risposta è arrivata facilmente - prima di correre getopts.

i=0
until [ "$((i=$i+1))" -gt "$#" ]
do case "$1"                   in
--Recursive) set -- "$@" "-R"  ;;
--file)      set -- "$@" "-f"  ;;
--target)    set -- "$@" "-t"  ;;
*)           set -- "$@" "$1"  ;;
esac; shift; done

La conversione di opzioni lunghe convertirà anche le non opzioni (come cp -t --file fooo cp -- --file foo) e non gestirà le opzioni immesse in forma abbreviata ( --fi...) o con la --target=/destsintassi.
Stéphane Chazelas,

@ StéphaneChazelas - la lunga conversione è solo un esempio - ovviamente non è stata costruita molto bene. Ho sostituito la getoptscosa con una funzione molto più semplice.
Mikeserv,

@ StéphaneChazelas - per favore, guarda di nuovo. Mentre il lungo commento sull'opzione rimane giustificato, il primo - e il suo downvote di accompagnamento - non è più, come penso. Inoltre, penso che opts()abbia qualche attinenza con la tua stessa risposta, come opts()funziona in un singolo ciclo - toccando ogni argomento ma una volta - e lo fa in modo affidabile (per quanto posso dire) , portabile e senza una singola subshell.
Mikeserv,

resettare OPTIND è ciò che chiamo avviando un altro loop getopts. In effetti, che è l'analisi di diverse linee di comando / gruppi di opzioni ( -R, -t /mybackup, -f). Manterrò il mio voto negativo per ora poiché è ancora offuscato, stai usando $anon inizializzato e args= opts...probabilmente lascerà argsdisinserito (o impostato su quello che era prima) dopo i optsritorni in molte shell (che include bash).
Stéphane Chazelas,

@ StéphaneChazelas - che include bash- lo odio. Secondo me, se la funzione è una funzione shell corrente che dovrebbe essere mantenuta. Sto affrontando queste cose - poiché la funzione attuale, con più test, potrebbe essere fatta in modo robusto per gestire tutti i casi e persino per contrassegnare l' argomento con la sua opzione precedente, ma non sono d'accordo sul fatto che sia offuscata . Come scritto, è il mezzo più semplice e diretto per svolgere il suo compito a cui riesco a pensare. testing quindi shifting ha poco senso quando in ogni shiftcaso fallito dovresti return. Non è destinato a fare diversamente.
Mikeserv,
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.