Come scrivere uno script che funziona con o senza argomenti?


13

Ho uno script bash che va in questo modo:

#!/bin/bash

if [ $1 = "--test" ] || [ $1 = "-t" ]; then
    echo "Testing..."
    testing="y"

else
    testing="n"
    echo "Not testing."

fi

Quindi quello che voglio poter fare non è solo eseguirlo con ./script --testo ./script -t, ma anche senza argomenti (solo ./script), ma a quanto pare se lo faccio con il codice corrente l'output è solo:

./script: line 3: [: =: unary operator expected
./script: line 3: [: =: unary operator expected
Not testing.

Quindi, come posso programmarlo in modo che eseguirlo senza argomenti farà semplicemente ciò elsesenza generare l'errore? Che cosa sto facendo di sbagliato?


1
Hai bisogno di doppie parentesi attorno ai tuoi assegni. [[]]
Terrance

3
Vedi Bash
pitfall

@Terrance non ne hai bisogno , anche se dovresti usarli se scegli come bersaglio bash.
Ruslan,

Risposte:


1

Il "modo corretto" di usare opzioni lunghe negli script di shell è tramite l' utilità getopt GNU . Ci sono anche getopts che sono integrati in bash , ma consentono solo opzioni brevi come -t. Alcuni esempi di getoptutilizzo sono disponibili qui .

Ecco una sceneggiatura che dimostra come affrontare la tua domanda. La spiegazione della maggior parte dei passaggi viene aggiunta come commento all'interno dello script stesso.

#!/bin/bash

# GNU getopt allows long options. Letters in -o correspond to
# comma-separated list given in --long.

opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened

set -- $opts # some would prefer using eval set -- "$opts"
# if theres -- as first argument, the script is called without
# option flags
if [ "$1" = "--"  ]; then
    echo "Not testing"
    testing="n"
    # Here we exit, and avoid ever getting to argument parsing loop
    # A more practical case would be to call a function here
    # that performs for no options case
    exit 1
fi

# Although this question asks only for one 
# option flag, the proper use of getopt is with while loop
# That's why it's done so - to show proper form.
while true; do
    case "$1" in
        # spaces are important in the case definition
        -t | --test ) testing="y"; echo "Testing" ;;
    esac
    # Loop will terminate if there's no more  
    # positional parameters to shift
    shift  || break
done

echo "Out of loop"

Con alcune semplificazioni e commenti rimossi, questo può essere condensato in:

#!/bin/bash
opts=$(getopt -o t --long test -- "$*")
test $? -ne 0 && exit 2 # error happened
set -- $opts    
case "$1" in
    -t | --test ) testing="y"; echo "Testing";;
    --) testing="n"; echo "Not testing";;
esac

16

Svariati modi; i due più ovvi sono:

  • Metti $1tra virgolette:if [ "$1" = "--test" ]
  • Controlla il numero di argomenti usando $ #

Meglio ancora, usa getopts.


2
+1 per l'utilizzo di getopts. Questo è il modo migliore per andare.
Seth,

3
Un altro linguaggio comune è [ "x$1" = "x--test" ]difendere nuovamente gli argomenti della riga di comando che sono operatori validi per il [comando. (Questo è un altro motivo per cui [[ ]]è preferito: fa parte della sintassi della shell, non solo un comando incorporato.)
Peter Cordes,

7

Devi citare le tue variabili all'interno della condizione if. Sostituire:

if [ $1 = "--test" ] || [ $1 = "-t" ]; then

con:

if [ "$1" = '--test' ] || [ "$1" = '-t' ]; then  

Quindi funzionerà:

➜ ~ ./test.sh
Non testato.

Cita sempre sempre due volte le tue variabili!


Le virgolette singole sono essenziali o si possono invece usare le virgolette doppie?

La risposta precisa dipende da quale shell stai usando, ma dalla tua domanda suppongo che tu stia usando un discendente Bourne-shell. La differenza principale tra virgolette singole e doppie è che l'espansione variabile avviene tra virgolette doppie, ma non tra virgolette singole: $ FOO = bar $ echo "$ FOO" bar $ echo '$ FOO' $ FOO (hmm .... can ' codice t nei commenti ...)
JayEye,

@ParanoidPanda Non devi usare virgolette singole, no, ma è buona norma usarle su letterali stringa. In questo modo non ti stupirai in seguito se i tuoi dati di stringa hanno un simbolo che bash vuole espandere.
Seth,

@ParanoidPanda Cosa ha detto @JayEye: Con le virgolette singole non c'è un'espansione variabile che foo=bar echo '$foo'stampa $foosull'output, ma foo=bar echo "$foo"stampa barinvece.
EKons,

4

Stai usando bash. Bash ha una grande alternativa a [: [[. Con [[, non devi preoccuparti di quotare e ottieni molti più operatori di [, incluso il supporto adeguato per ||:

if [[ $1 = "--test" || $1 = "-t" ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Oppure puoi usare le espressioni regolari:

if [[ $1 =~ ^(--test|-t) ]]
then
    echo "Testing..."
    testing="y"
else
    testing="n"
    echo "Not testing."
fi

Ovviamente, si dovrebbe sempre fare una quotazione corretta, ma non c'è motivo di attenersi [quando si utilizza bash.


3

Per verificare se sono presenti argomenti (in generale e eseguire le azioni di conseguenza) Questo dovrebbe funzionare:

#!/bin/bash

check=$1

if [ -z "$check" ]; then
  echo "no arguments"
else echo "there was an argument"
fi

Perché no [ -z "$1" ]?
EKons,

@ ΈρικΚωνσταντόπουλος In questo caso, certo, normalmente comunque negli script, chiamo la variabile una sola volta, mi riferisco ad essa nelle procedure, che di solito è più di una volta. Ha deciso di mantenere il protocollo nell'esempio.
Jacob Vlijm,

In questo esempio ti sembra di usare $checkuna volta, prima di ogni shifts, quindi sarebbe meglio usare $1invece.
EKons,

@ ΈρικΚωνσταντόπουλος Ovviamente, come detto, non è un esempio da utilizzare negli script. Negli script, è una cattiva pratica ripetere più volte lo stesso.
Jacob Vlijm,

Capisco cosa intendi, ma è meglio non usare shebang #!quando si forniscono esempi (dovresti descrivere la shell al di fuori del codice).
EKons,
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.