Qual è un modo conciso per verificare che le variabili di ambiente siano impostate in uno script della shell Unix?


500

Ho alcuni script di shell Unix in cui devo verificare che determinate variabili di ambiente siano impostate prima di iniziare a fare cose, quindi faccio questo genere di cose:

if [ -z "$STATE" ]; then
    echo "Need to set STATE"
    exit 1
fi  

if [ -z "$DEST" ]; then
    echo "Need to set DEST"
    exit 1
fi

che è molto digitando. Esiste un linguaggio più elegante per verificare che sia impostato un set di variabili d'ambiente?

EDIT: dovrei menzionare che queste variabili non hanno un valore predefinito significativo: lo script dovrebbe essere errato se non è impostato.


2
Molte delle risposte a questa domanda sembrano qualcosa che vedresti sullo scambio di code code stack .
Daniel Schilling,

Risposte:


587

Espansione dei parametri

La risposta ovvia è utilizzare una delle forme speciali di espansione dei parametri:

: ${STATE?"Need to set STATE"}
: ${DEST:?"Need to set DEST non-empty"}

O meglio (vedere la sezione "Posizione delle virgolette doppie" di seguito):

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

La prima variante (usando solo ?) richiede che STATE sia impostato, ma STATE = "" (una stringa vuota) è OK - non esattamente quello che vuoi, ma l'alternativa e la notazione precedente.

La seconda variante (utilizzo :?) richiede che DEST sia impostato e non vuoto.

Se non si fornisce alcun messaggio, la shell fornisce un messaggio predefinito.

Il ${var?}costrutto è portatile alla versione 7 UNIX e Bourne Shell (1978 o successivi). Il ${var:?}costrutto è leggermente più recente: penso che fosse in System III UNIX intorno al 1981, ma potrebbe essere stato in PWB UNIX prima. È quindi nella shell Korn e nelle shell POSIX, incluso in particolare Bash.

Di solito è documentato nella pagina man della shell in una sezione chiamata Parameter Expansion . Ad esempio, il bashmanuale dice:

${parameter:?word}

Visualizza errore se Null o Unset. Se il parametro è nullo o non impostato, l'espansione della parola (o un messaggio in tal senso se la parola non è presente) viene scritta nell'errore standard e la shell, se non è interattiva, viene chiusa. Altrimenti, viene sostituito il valore del parametro.

Il comando dei due punti

Dovrei probabilmente aggiungere che il comando due punti ha semplicemente valutato i suoi argomenti e poi ha esito positivo. È la notazione originale del commento della shell (prima di ' #' alla fine della riga). Per molto tempo, gli script di shell Bourne avevano i due punti come primo carattere. La shell C leggeva una sceneggiatura e utilizzava il primo carattere per determinare se era per la shell C (un ' #' hash) o la shell Bourne (un ' :' due punti). Quindi il kernel è entrato in azione e ha aggiunto il supporto per ' #!/path/to/program' e la shell Bourne ha ottenuto ' #' commenti, e la convenzione del colon è andata avanti. Ma se ti imbatti in una sceneggiatura che inizia con due punti, ora saprai perché.


Posizione delle doppie virgolette

blong ha chiesto in un commento :

Qualche idea su questa discussione? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

L'essenza della discussione è:

... Tuttavia, quando lo faccio shellcheck(con la versione 0.4.1), ottengo questo messaggio:

In script.sh line 13:
: ${FOO:?"The environment variable 'FOO' must be set and non-empty"}
  ^-- SC2086: Double quote to prevent globbing and word splitting.

Qualche consiglio su cosa dovrei fare in questo caso?

La risposta breve è "fai come shellchecksuggerisce":

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Per illustrare il motivo, studiare quanto segue. Nota che il :comando non fa eco ai suoi argomenti (ma la shell valuta gli argomenti). Vogliamo vedere gli argomenti, quindi il codice seguente usa printf "%s\n"al posto di :.

$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$ 
$ x="*"
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
bash: x: You must set x
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
*
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ 

Si noti come il valore in $xviene prima espanso *e poi un elenco di nomi di file quando l'espressione complessiva non è tra virgolette. Questo è ciò che shellcheckraccomanda dovrebbe essere risolto. Non ho verificato che non si oppone al modulo in cui l'espressione è racchiusa tra virgolette doppie, ma è ragionevole supporre che sarebbe OK.


2
Questa è la cosa di cui ho bisogno. Sto usando varie versioni di Unix dal 1987 e non ho mai visto questa sintassi - va solo per mostrare ...
AndrewR

Come funziona e dove è documentato? Mi chiedo se può essere modificato per verificare che la variabile esista e sia impostata su un valore specifico.
Jhabbott,

6
È documentato nella pagina del manuale della shell, o nel manuale di Bash, di solito sotto l'intestazione "Parameter Expansion". Il metodo standard per verificare se esiste la variabile ed è impostata su un valore specifico (dire 'abc') è semplicemente: if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi.
Jonathan Leffler,

Come posso usare questa sintassi con una variabile stringa con spazi all'interno. Viene visualizzato un errore che dice "Questo: comando non trovato" quando imposto la variabile che voglio controllare su "Questo è un test". Qualche idea?
user2294382

1
Qualche idea su questa discussione? github.com/koalaman/shellcheck/issues/…
blong

138

Prova questo:

[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;

8
È piuttosto prolisso. Il punto e virgola è ridondante.
Jonathan Leffler,

3
O ancora più breve [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } vedi questo post sul blog
zipizap il

36
Ciò che è, è leggibile. Sono tutto per questo. Gli script Bash hanno bisogno di tutto l'aiuto che possono ottenere in quel reparto!
Iain Duncan,

la risposta originale è più coerente nella sua sintassi.
dente veloce

4
Ciò, tuttavia, non distingue tra una variabile non impostata e una variabile impostata sulla stringa vuota.
Chepner,

56

La tua domanda dipende dalla shell che stai usando.

Bourne shell lascia ben poco in termini di ciò che stai cercando.

MA...

Funziona, praticamente ovunque.

Prova a stare lontano da csh. Era buono per le campane e i fischi che ha aggiunto, rispetto al guscio di Bourne, ma ora è davvero scricchiolante. Se non mi credi, prova a separare STDERR in csh! (-:

Ci sono due possibilità qui. L'esempio sopra, ovvero usando:

${MyVariable:=SomeDefault}

per la prima volta devi fare riferimento a $ MyVariable. Questo richiede l'ambiente. var MyVariable e, se non è attualmente impostato, assegna il valore di SomeDefault alla variabile per un uso successivo.

Hai anche la possibilità di:

${MyVariable:-SomeDefault}

che sostituisce SomeDefault con la variabile in cui si utilizza questo costrutto. Non assegna il valore SomeDefault alla variabile e il valore di MyVariable sarà comunque nullo dopo il rilevamento di questa istruzione.


3
CSH: (foo> foo.out)> & foo.err
Mr.Ree,

La shell Bourne fa ciò che è richiesto.
Jonathan Leffler,

51

Sicuramente l'approccio più semplice è quello di aggiungere il -upassaggio allo shebang (la linea nella parte superiore dello script), supponendo che tu stia utilizzando bash:

#!/bin/sh -u

Ciò causerà la chiusura dello script se si annidano eventuali variabili non associate.


40
${MyVariable:=SomeDefault}

Se MyVariableè impostato e non è nullo, reimposterà il valore della variabile (= non succede nulla).
Altrimenti, MyVariableè impostato su SomeDefault.

Quanto sopra tenterà di essere eseguito ${MyVariable}, quindi se vuoi solo impostare la variabile fai:

MyVariable=${MyVariable:=SomeDefault}

Questo non genera il messaggio e la Q dice che non esiste alcun valore predefinito (anche se non lo ha detto quando hai digitato la tua risposta).
Jonathan Leffler,

10
Versioni corrette: (1): $ {MyVariable: = SomeDefault} o (2): MyVariable = $ {MyVariable: -SomeDefault}
Jonathan Leffler

23

Secondo me il controllo più semplice e compatibile per #! / Bin / sh è:

if [ "$MYVAR" = "" ]
then
   echo "Does not exist"
else
   echo "Exists"
fi

Ancora una volta, questo è per / bin / sh ed è compatibile anche con i vecchi sistemi Solaris.


Questo funziona, ma anche i vecchi sistemi Solaris /bin/shsupportano la ${var:?}notazione, ecc.
Jonathan Leffler,

La migliore risposta finora.
Ole,

4
Essere impostato sulla stringa vuota è distinto dal non essere impostato affatto. Questo test stampa "Non esiste" dopo entrambi unset MYVARe MYVAR=.
Chepner,

@chepner, ho valutato il tuo commento per l'heads-up presumibilmente prezioso, ma poi ho continuato a testarlo (con bash) e non ho potuto impostare un var su una stringa vuota senza che fosse anche disinserita. Come lo faresti?
Sz.

@Sz Non sono sicuro di cosa intendi. "Unset" significa che il nome non ha alcun valore assegnato ad esso. Se foonon è impostato, "$foo"si espande ancora nella stringa vuota.
Chepner,

17

bash4.2 ha introdotto l' -voperatore che verifica se un nome è impostato su qualsiasi valore, anche la stringa vuota.

$ unset a
$ b=
$ c=
$ [[ -v a ]] && echo "a is set"
$ [[ -v b ]] && echo "b is set"
b is set
$ [[ -v c ]] && echo "c is set"
c is set

16

Ho sempre usato:

if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi

Non molto più conciso, temo.

Sotto CSH hai $? STATE.


2
Controlla se STATEè vuoto o non impostato, non solo non impostato.
Chepner,

7

Per le persone future come me, volevo fare un passo avanti e parametrizzare il nome var, quindi posso scorrere su un elenco di nomi variabili variabili:

#!/bin/bash
declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN)

for var_name in "${vars[@]}"
do
  if [ -z "$(eval "echo \$$var_name")" ]; then
    echo "Missing environment variable $var_name"
    exit 1
  fi
done

3

Possiamo scrivere una bella affermazione per controllare un mucchio di variabili contemporaneamente:

#
# assert if variables are set (to a non-empty string)
# if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise
#
# Usage: assert_var_not_null [-f] variable ...
#
function assert_var_not_null() {
  local fatal var num_null=0
  [[ "$1" = "-f" ]] && { shift; fatal=1; }
  for var in "$@"; do
    [[ -z "${!var}" ]] &&
      printf '%s\n' "Variable '$var' not set" >&2 &&
      ((num_null++))
  done

  if ((num_null > 0)); then
    [[ "$fatal" ]] && exit 1
    return 1
  fi
  return 0
}

Invocazione di esempio:

one=1 two=2
assert_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_not_null -f one two three
echo test 3: return_code=$? # this code shouldn't execute

Produzione:

test 1: return_code=0
Variable 'three' not set
test 2: return_code=1
Variable 'three' not set

Altre asserzioni del genere qui: https://github.com/codeforester/base/blob/master/lib/assertions.sh



1

Nessuna delle soluzioni di cui sopra ha funzionato per i miei scopi, in parte perché ho verificato l'ambiente per un elenco aperto di variabili che devono essere impostate prima di iniziare un lungo processo. Ho finito con questo:

mapfile -t arr < variables.txt

EXITCODE=0

for i in "${arr[@]}"
do
   ISSET=$(env | grep ^${i}= | wc -l)
   if [ "${ISSET}" = "0" ];
   then
      EXITCODE=-1
      echo "ENV variable $i is required."
   fi
done

exit ${EXITCODE}

-1

Anziché utilizzare script di shell esterni, tendo a caricare le funzioni nella mia shell di accesso. Uso qualcosa del genere come funzione di supporto per verificare le variabili di ambiente anziché qualsiasi variabile impostata:

is_this_an_env_variable ()
    local var="$1"
    if env |grep -q "^$var"; then
       return 0
    else
       return 1
    fi
 }

1
Questo è sbagliato. is_this_an_env_variable Prestituirà il successo perché PATHesiste.
Eric

Esiste perché è una variabile d'ambiente. Se è stato impostato eqaul nonnull string è un'altra questione.
nafdef,

-7

La $?sintassi è piuttosto ordinata:

if [ $?BLAH == 1 ]; then 
    echo "Exists"; 
else 
    echo "Does not exist"; 
fi

Funziona, ma qualcuno qui sa dove posso trovare documenti su quella sintassi? $? è normalmente il valore di ritorno precedente.
Danny Staple,

Mio male - questo non sembra funzionare. Provalo in un semplice script, con e senza quel set.
Danny Staple,

11
Nelle shell Bourne, Korn, Bash e POSIX $?è lo stato di uscita del comando precedente; $?BLAHè la stringa costituita dallo stato di uscita del comando precedente concatenato con 'BLAH'. Questo non verifica affatto la variabile $BLAH. (Si dice che potrebbe fare più o meno ciò che è richiesto in csh, ma è meglio lasciare le conchiglie sulla riva del mare.)
Jonathan Leffler

Questa è una risposta completamente sbagliata per quanto riguarda Bash.
codeforester
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.