Bash Getopts, nur kurze Optionen, alle erfordern Werte, eigene Validierung

8

Ich versuche, ein Shell-Skript aufzubauen, das verschiedene Optionen akzeptiert und getoptseine gute Lösung zu sein scheint, da es die variable Reihenfolge der Optionen und Argumente handhaben kann (glaube ich!).

Ich verwende nur kurze Optionen und für jede kurze Option ist ein entsprechender Wert erforderlich, z. B.: ./command.sh -a arga -g argg -b argbIch möchte jedoch zulassen, dass die Optionen in einer unspezifischen Reihenfolge eingegeben werden, wie es die meisten Leute gewohnt sind, mit Shell-Befehlen zu arbeiten .

Der andere Punkt ist, dass ich meine eigenen Überprüfungen der Optionsargumentwerte durchführen möchte, idealerweise innerhalb der caseAnweisungen. Der Grund dafür ist, dass meine Tests :)in meiner caseErklärung zu inkonsistenten Ergebnissen geführt haben (wahrscheinlich durch mangelndes Verständnis meinerseits).
Zum Beispiel:

#!/bin/bash
OPTIND=1 # Reset if getopts used previously
if (($# == 0)); then
        echo "Usage"
        exit 2
fi
while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                :)
                       echo "Option -$OPTARG requires an argument" >&2
                       exit 2;;
        esac
done
shift $((OPTIND-1))
echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

War bei solchen Ereignissen fehlgeschlagen ... ./command.sh -d -h
Wenn ich möchte, dass -d als Argument erforderlich markiert wird, aber ich erhalte, dessen Wert -d=-hnicht das ist, was ich brauche.

Daher dachte ich, es wäre einfacher, meine eigene Validierung innerhalb der case-Anweisungen durchzuführen, um sicherzustellen, dass jede Option nur einmal festgelegt und festgelegt wird.

Ich versuche Folgendes zu tun, aber meine if [ ! "$MYSQL_HOST" ]; thenBlöcke werden nicht ausgelöst.

OPTIND=1 # Reset if getopts used previously

if (($# == 0)); then
        echo "Usage"
        exit 2
fi

while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        if [ ! "$MYSQL_HOST" ]; then
                                echo "host not set"
                                exit 2
                        fi
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        if [ ! "$MYSQL_USER" ]; then
                                echo "username not set"
                                exit 2
                        fi
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        if [ ! "$MYSQL_PASS" ]; then
                                echo "password not set"
                                exit 2
                        fi
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        if [ ! "$BACKUP_DIR" ]; then
                                echo "backup dir not set"
                                exit 2
                        fi
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                #:)
                #       echo "Option -$opt requires an argument" >&2
                #       exit 2;;
        esac
done
shift $((OPTIND-1))

echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

Gibt es einen Grund, warum ich nicht überprüfen kann, ob ein OPTARGvon innen eine Länge von Null hat getopts ... while ... case?

Was ist der bessere Weg , mein eigenes Argument Validierung mit laufen getoptsin einem Fall , wo ich will auf das zu verlassen nicht :). Führen Sie meine Argumentvalidierung außerhalb des while ... case ... esac?
Dann könnte ich mit Argumentwerten von -detc enden und keine fehlende Option abfangen.

Batfastad
quelle

Antworten:

4

Wenn Sie Ihr zweites Skript aufrufen (ich habe es als gespeichert getoptit) mit:

getoptit -d -h

Dies wird gedruckt:

MYSQL_HOST=''  MYSQL_USER=''  MYSQL_PASS=''  BACKUP_DIR='-h' Additionals: 

BACKUP_DIR ist also gesetzt, und Sie testen, if [ ! "$BACKUP_DIR" ]; thenob es nicht gesetzt ist. Daher ist es normal, dass der darin enthaltene Code nicht ausgelöst wird.

Wenn Sie testen möchten, ob jede Option einmal festgelegt ist, müssen Sie dies tun, bevor Sie die Zuweisung aus dem Wert $ OPTARG vornehmen. Und Sie sollten wahrscheinlich auch prüfen, ob $ OPTARG mit einem '-'(für den -d -hFehler) beginnt, bevor Sie Folgendes zuweisen:

...
            d)
                    if [ ! -z "$BACKUP_DIR" ]; then
                            echo "backup dir already set"
                            exit 2
                    fi
                    if [ z"${OPTARG:0:1}" == "z-" ]; then
                            echo "backup dir starts with option string"
                            exit 2
                    fi
                    BACKUP_DIR=$OPTARG
                    ;;
...
Anthon
quelle
Hervorragende Erklärung. Sinnvoll, wenn ich darüber nachdenke, dass tatsächlich eine whileSchleife über die Optionen läuft. Dies ist die Methode, mit der ich mich entschieden habe, obwohl sie im Vergleich zu anderen ausführlich ist, ist sehr klar, was im Skript vor sich geht. In diesem Fall ist die Wartbarkeit durch andere wichtig.
Batfastad
3

Da die anderen Antworten Ihre Frage nicht so oft beantworten: Dies ist das erwartete Verhalten und basiert darauf, wie POSIX interpretiert wird:

Wenn für die Option ein Optionsargument erforderlich ist, muss das Dienstprogramm getopts es in die Shell-Variable OPTARG einfügen. Wenn keine Option gefunden wurde oder wenn die gefundene Option kein Optionsargument hat, wird OPTARG deaktiviert.)

Dies ist alles, was gesagt wird, und um es kurz zu machen: getoptsWeiß nicht, dass das vollkommen gültige Argument, das es für die Option sieht, für die Sie ein Argument benötigen , eigentlich kein Optionsargument, sondern eine andere Option ist . Nichts hindert Sie daran, -hein gültiges Argument für die -dOption zu haben. In der Praxis bedeutet dies, dass getoptsnur dann ein Fehler ausgegeben wird, wenn Ihre Option, für die ein Argument erforderlich ist, als letztes in der Befehlszeile angezeigt wird, z.

test.sh::

#!/bin/sh

while getopts ":xy:" o; do
    case "${o}" in
        :) echo "${OPTARG} requires an argument"; exit 1;
    esac
done

Beispiel:

$ ./test.sh -y -x
$

$ ./test.sh -x -y
y requires an argument

Der Grund, warum Ihr zweiter Ansatz nicht funktioniert, liegt darin, dass die Analyse getoptsimmer noch dieselbe ist. Sobald Sie sich also in der Schleife befinden, ist der "Schaden" bereits angerichtet.

Wenn Sie dies unbedingt verbieten möchten, müssen Sie, wie Benubird betonte, Ihre Optionsargumente selbst überprüfen und einen Fehler auslösen, wenn sie einer gültigen Option entsprechen.

Adrian Frühwirth
quelle
2

Ich würde weiterhin verwenden, getoptsaber einige zusätzliche Überprüfungen durchführen, nachdem: (ungetestet)

errs=0
declare -A option=(
    [MYSQL_HOST]="-h"
    [MYSQL_USER]="-u"
    [MYSQL_PASS]="-p"
    [BACKUP_DIR]="-d" 
)
for var in "${!option[@]}"; do
    if [[ -z "${!var}" ]]; then
        echo "error: specify a value for $var with ${option[var]}"
        ((errs++))
    fi
done
((errs > 0)) && exit 1

erfordert Bash Version 4

Glenn Jackman
quelle
1

Das in Gnu integrierte getopt ohne Shell erledigt weniger Arbeit für Sie, bietet jedoch mehr Flexibilität, da Sie bestimmen können, was passiert, nachdem ein Flag gefunden wurde, einschließlich des Verschiebens der Argumente selbst.

Ich habe einen Artikel gefunden , in dem getopts und getopt verglichen werden, was wahrscheinlich hilfreich sein wird, da das Handbuch (zumindest vor dem Kaffee) schwer zu verstehen ist.

msw
quelle
1

Zunächst sollten Sie verwenden if [ -z "$MYSQL_USER" ].

Zweitens besteht keine Notwendigkeit für die Zuordnung - if [ -z "$OPTARG" ]wird gut funktionieren.

Drittens vermute ich, was Sie tatsächlich wollen, ist if [ ${#OPTARG} = 0 ]. $ {# x} ist eine Bash-Sache, die die Länge der Zeichenfolge $ x zurückgibt (mehr dazu hier ).

Viertens würde ich, wenn Sie Ihre eigene Validierung durchführen, getopt anstelle von getopts empfehlen, da dies viel mehr Flexibilität bietet.

Um Ihre erste Frage zu beantworten, können Sie schließlich feststellen, wann ein Flag als Argument übergeben wird, indem Sie eine Liste von Flags wie folgt oben einfügen:

args=( -h -u -p -d )

Und dann überprüfen Sie die Option, bei der Sie überprüfen möchten, ob das angegebene Argument eine Option ist.

d)
    BACKUP_DIR=$OPTARG
    if [ "$(echo ${args[@]/$OPTARG/})" = "$(echo ${args[@]})" ]
    then
        echo "Argument is an option! Error!"
    fi

Keine perfekte Antwort, aber es funktioniert! Ihr Problem ist, dass "-h" ein vollkommen gültiges Argument ist und Sie der Shell keine Möglichkeit geben zu wissen, dass sie nur nach Parametern sucht, die keine gültigen Flags sind.

Benubird
quelle