Wie analysiere ich optionale Argumente in einem Bash-Skript, wenn keine Reihenfolge angegeben ist?

13

Ich bin verwirrt, wie optionale Argumente / Flags beim Schreiben eines Bash-Skripts für das folgende Programm eingefügt werden sollen:

Das Programm erfordert zwei Argumente:

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

Es gibt jedoch mehrere optionale Flags:

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

Ich möchte das Bash-Skript so ausführen, dass es Benutzerargumente akzeptiert. Wenn Benutzer nur zwei Argumente der Reihe nach eingeben, wäre dies:

#!/bin/sh

run_program --flag1 $1 --flag2 $2

Was aber, wenn eines der optionalen Argumente enthalten ist? Ich würde denken, dass es sein würde

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

Aber was ist, wenn 4 $ gegeben werden, aber nicht 3 $?

ShanZhengYang
quelle
getoptsist was du willst. Ohne dies könnten Sie eine Schleife mit einer switch-Anweisung verwenden, um jedes optionale oder nicht optionale Flag zu erkennen.
Orion
@orion Mit getoptsmüsste ich jede Argumentkombination angeben? 3 & 4, 3 & 5, 3 & 4 & 5 usw.?
ShanZhengYang
Nein, Sie legen sie nur fest, wenn Sie sie erhalten, andernfalls wird gemeldet, dass sie nicht gefunden wurden. Im Grunde genommen "erhalten" Sie einfach jede Option in beliebiger Reihenfolge und geben sie in beliebiger Reihenfolge an, wenn überhaupt. Aber lesen Sie einfach die Bash-Manpage, es ist alles da.
Orion
@orion Es tut mir leid, aber ich verstehe immer noch nicht ganz getopts. Angenommen, ich zwinge Benutzer, das Skript mit allen Argumenten auszuführen: run_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSEdas Programm wird ausgeführt als program --flag1 VAL --flag2 VAL. Wenn Sie ausgeführt werden run_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE, wird das Programm als ausgeführt program --flag1 VAL --flag2 VAL --optflag2 10. Wie können Sie ein solches Verhalten erreichen getopts?
ShanZhengYang

Antworten:

24

Dieser Artikel zeigt zwei verschiedene Möglichkeiten - shiftund getopts(und erläutert die Vor- und Nachteile der beiden Ansätze).

Wenn shiftIhr Skript betrachtet wird $1, entscheidet es, welche Aktion ausgeführt werden soll, und führt shiftsie dann aus , wechselt $2zu $1, $3zu $2usw.

Beispielsweise:

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

Mit getoptsdefinieren Sie die (kurzen) Optionen im whileAusdruck:

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

Offensichtlich sind dies nur Code-Schnipsel, und ich habe die Validierung ausgelassen - um zu überprüfen, ob die obligatorischen Argumente flag1 und flag2 gesetzt sind usw.

Welchen Ansatz Sie verwenden, ist bis zu einem gewissen Grad Geschmackssache - wie portabel Ihr Skript sein soll, ob Sie nur mit kurzen (POSIX) Optionen leben können oder ob Sie lange (GNU) Optionen wünschen usw.

John N.
quelle
Ich mache normalerweise while (( $# ))statt while :;und oft mit einem Fehler in dem *Fall zu beenden
Jasen
Dies beantwortet den Titel, aber nicht die Frage.
Jasen
@Jasen Ich habe mir diese letzte Nacht angeschaut und konnte für mein Leben nicht verstehen warum. Heute morgen ist es viel klarer (und ich habe jetzt auch die Antwort von Orion gesehen). Ich werde diese Antwort später heute löschen (ich wollte Ihren Kommentar zuerst bestätigen, und dies schien der einfachste Weg zu sein, dies zu tun).
John N
Kein Problem, es ist immer noch eine gute Antwort, nur die Frage ist ihm ausgewichen.
Jasen
1
NB: Im Falle set -o nounsetder ersten Lösung tritt ein Fehler auf, wenn kein Parameter angegeben wird. Fix:case ${1:-} in
Raphael
3

Verwenden Sie ein 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[@]}"

Dadurch werden Argumente mit Leerzeichen korrekt behandelt.

[Bearbeiten] Ich habe die ungefähr äquivalente Syntax verwendet, args=( "${args[@]}" --optflag1 "$3" )aber G-Man schlug einen besseren Weg vor.

Jasen
quelle
1
Sie können dies ein wenig rationalisieren, indem Sie sagen args+=( --optflag1 "$3" ). Vielleicht möchten Sie meine Antwort auf unser Nachschlagewerk, Auswirkungen auf die Sicherheit , wenn eine Variable nicht in Bash / POSIX-Shells zitiert wird, sehen, in denen ich diese Technik diskutiere (das Erstellen einer Befehlszeile in einem Array durch bedingtes Anhängen optionaler Argumente).
G-Man sagt "Reinstate Monica"
@ G-Man Danke, ich hatte nicht gesehen, dass ich in der Dokumentation [@]ein ausreichendes Werkzeug hatte, um mein Problem zu lösen.
Jasen
1

In einem Shell-Skript lauten die Argumente "$ 1", "$ 2", "$ 3" usw. Die Anzahl der Argumente beträgt $ #.
Wenn Ihr Skript keine Optionen erkennt, können Sie die Optionserkennung weglassen und alle Argumente als Operanden behandeln.
Verwenden Sie die integrierten getopts, um Optionen zu erkennen

Michael Durrant
quelle
0

Wenn Ihre Eingabeoptionen positionell sind (Sie wissen, an welchen Stellen sie sich befinden) und nicht mit Flags angegeben sind, möchten Sie lediglich die Befehlszeile erstellen. Bereiten Sie einfach die Befehlsargumente für alle vor:

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

Wenn die Parameter nicht angegeben werden, sind die entsprechenden Zeichenfolgen leer und werden zu nichts erweitert. Beachten Sie, dass in der letzten Zeile keine Anführungszeichen stehen. Das liegt daran, dass die Shell die Parameter in Wörter aufteilen soll (geben --flag1und$1 als separate Argumente für Ihr Programm). Dies geht natürlich schief, wenn Ihre ursprünglichen Parameter Leerzeichen enthalten. Wenn Sie derjenige sind, der dies ausführt, können Sie es verlassen. Wenn es sich jedoch um ein allgemeines Skript handelt, kann es zu unerwartetem Verhalten kommen, wenn der Benutzer etwas mit Leerzeichen eingibt. Um damit umzugehen, müssen Sie den Code etwas hässlicher machen.

Das xPräfix im []Test ist für den Fall vorhanden $3oder $4leer. In diesem Fall würde bash erweitern [ FALSE != $3 ]in[ FALSE != ] dem ein Syntaxfehler, so ein weiteres beliebige Zeichen gibt es gegen diese zu schützen. Dies ist eine sehr häufige Methode, die Sie in vielen Skripten sehen werden.

Ich habe OPTFLAG1und der Rest von ihnen zu ""Beginn eingestellt, nur um sicherzugehen (falls sie zuvor auf etwas eingestellt waren), aber wenn sie nicht tatsächlich in der Umgebung deklariert wurden, müssen Sie dies nicht unbedingt tun.

Ein paar zusätzliche Bemerkungen:

  • Sie können die Parameter tatsächlich genauso empfangen wie runprogrammit Flags. Darum geht John Nes. Dort getoptswird es nützlich.
  • Die Verwendung von FALSE für diesen Zweck ist etwas unstandardisiert und ziemlich lang. Normalerweise würde man ein einzelnes Zeichen (möglicherweise -) verwenden, um einen leeren Wert zu signalisieren, oder einfach eine leere Zeichenfolge übergeben "", wenn dies ansonsten kein gültiger Wert des Parameters ist. Leere Zeichenfolge macht den Test auch kürzer, verwenden Sie einfachif [ -n "$3" ] .
  • Wenn es nur ein optionales Flag gibt oder wenn diese abhängig sind, sodass Sie niemals OPTFLAG2, aber nicht OPTFLAG1 haben können, können Sie einfach diejenigen überspringen, die Sie nicht setzen möchten. Wenn Sie ""leere Parameter verwenden, wie oben vorgeschlagen, können Sie trotzdem alle nachgestellten Leergut überspringen.

Diese Methode ist so ziemlich die Art und Weise, wie Optionen an Compiler in Makefiles übergeben werden.

Und wieder - wenn Eingaben Leerzeichen enthalten können, wird es hässlich.

Orion
quelle
Nein, Sie möchten nicht, dass die Shell die Parameter in Wörter aufteilt. Sie sollten immer alle Verweise auf Shell-Variablen zitieren, es sei denn, Sie haben einen guten Grund, dies nicht zu tun, und Sie sind sicher, dass Sie wissen, was Sie tun. Siehe Sicherheit Auswirkungen der Fehler eine Variable in bash / POSIX - Shells zu zitieren , falls Sie nicht mit ihm vertraut sind, und nach unten scrollen , um meine Antwort , wo ich genau dieses Problem zu lösen (mit der gleichen Technik Jasen Anwendungen ).
G-Man sagt "Reinstate Monica"