bash getopts, solo opzioni brevi, tutti richiedono valori, propria convalida


8

Sto cercando di creare uno script di shell che accetta varie opzioni e getoptssembra una buona soluzione in quanto può gestire l'ordinamento variabile delle opzioni e degli argomenti (credo!).

Userò solo opzioni brevi e ogni opzione corta richiederà un valore corrispondente, ad esempio: ./command.sh -a arga -g argg -b argbma vorrei consentire l'inserimento delle opzioni in un ordine non specifico, come è il modo in cui la maggior parte delle persone è abituata a lavorare con i comandi della shell .

L'altro punto è che vorrei fare il mio controllo dei valori degli argomenti delle opzioni, idealmente all'interno delle casedichiarazioni. La ragione di ciò è che il mio test :)nella mia caseaffermazione ha prodotto risultati incoerenti (probabilmente per mancanza di comprensione da parte mia).
Per esempio:

#!/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: $@"

./command.sh -d -h
Stavo fallendo per eventi come questo ... Quando voglio che contrassegni -d come richiede un argomento, ma ottengo il valore di -d=-hcui non è quello di cui ho bisogno.

Quindi ho pensato che sarebbe stato più semplice eseguire la mia convalida all'interno delle dichiarazioni del caso per garantire che ogni opzione fosse impostata e impostata una sola volta.

Sto provando a fare quanto segue ma i miei if [ ! "$MYSQL_HOST" ]; thenblocchi non sono attivati.

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: $@"

C'è un motivo per cui non riesco a controllare se uno OPTARGha lunghezza zero dall'interno getopts ... while ... case?

Qual è il modo migliore per eseguire la mia convalida argomento getoptsin un caso in cui non voglio fare affidamento sul :). Eseguire la convalida del mio argomento al di fuori del while ... case ... esac?
Quindi potrei finire con i valori degli argomenti di -detc e non catturare un'opzione mancante.

Risposte:


4

Quando chiami il tuo secondo script (l'ho salvato come getoptit) con:

getoptit -d -h

Questo stamperà:

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

Quindi BACKUP_DIR è impostato e stai testando if [ ! "$BACKUP_DIR" ]; thense non è impostato, quindi è normale che il codice al suo interno non sia attivato.

Se si desidera verificare se ciascuna opzione è impostata una volta, è necessario farlo prima di eseguire l'assegnazione dal valore $ OPTARG. E probabilmente dovresti anche verificare che $ OPTARG inizi con un '-'(per l' -d -herrore) prima di assegnare:

...
            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
                    ;;
...

Spiegazione eccellente. Ha perfettamente senso una volta che ho pensato al fatto che esiste effettivamente un whileciclo che scorre sulle opzioni. Questo è il metodo con cui ho seguito, anche se è dettagliato rispetto ad altri, è molto chiaro cosa sta succedendo nella sceneggiatura. E la manutenibilità da parte di altri è importante in questo caso.
batfastad,

3

Poiché le altre risposte in realtà non rispondono così tanto alla tua domanda: questo è un comportamento previsto e basato sul modo in cui POSIX viene interpretato:

Quando l'opzione richiede un argomento-opzione, l'utility getopts la inserisce nella variabile shell OPTARG. Se non è stata trovata alcuna opzione o se l'opzione trovata non ha un argomento-opzione, OPTARG non sarà impostato.)

Questo è tutto ciò che viene affermato, e per getoptsfarla breve (non così lunga): non sa magicamente che l'argomento perfettamente valido che vede per l'opzione di cui hai bisogno per avere un argomento non è in realtà un argomento di opzione ma un'altra opzione . Nulla ti impedisce di avere -hun argomento valido per l' -dopzione, il che in pratica significa che getoptsgenererà un errore solo se l'opzione che richiede un argomento arriva per ultima sulla riga di comando, ad esempio:

test.sh:

#!/bin/sh

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

Esempio:

$ ./test.sh -y -x
$

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

Il motivo per cui il tuo secondo approccio non funziona è perché l'analisi che getoptsfa è sempre la stessa, quindi una volta che sei all'interno del ciclo il "danno" è già fatto.

Se vuoi assolutamente vietarlo, allora, come ha sottolineato Benubird, dovrai controllare tu stesso gli argomenti delle tue opzioni e lanciare un errore se equivalgono a un'opzione valida.


2

Continuerei a usare getoptsma farei qualche controllo extra dopo: (non testato)

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

richiede la versione 4 di bash


1

Il getopt incorporato non-shell di Gnu fa meno lavoro per te ma consente una maggiore flessibilità man mano che riesci a determinare cosa succede dopo aver trovato un flag, incluso lo spostamento degli argomenti da solo.

Ho trovato un articolo che confronta getopts e getopt che probabilmente sarà utile in quanto il manuale è un po 'difficile da dare un senso (almeno prima del caffè).


1

Innanzitutto, dovresti usare if [ -z "$MYSQL_USER" ].

In secondo luogo, non è necessario per il compito: if [ -z "$OPTARG" ]funzionerà bene.

In terzo luogo, sospetto che ciò che realmente desideri sia if [ ${#OPTARG} = 0 ]. $ {# x} è una cosa bash che restituisce la lunghezza della stringa $ x (vedi di più qui ).

In quarto luogo, se stai eseguendo la tua convalida, consiglierei getopt invece di getopts, poiché offre molta più flessibilità.

Infine, per rispondere alla tua prima domanda, puoi rilevare quando un flag viene passato come argomento mettendo un elenco di flag in alto, in questo modo:

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

E quindi avere un controllo nell'opzione in cui si desidera verificare se l'argomento fornito è un'opzione, qualcosa del genere:

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

Non è una risposta perfetta, ma funziona! Il tuo problema è che "-h" è un argomento perfettamente valido e non dai modo alla shell di sapere che sta cercando solo parametri che non sono anche flag validi.

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.