Come sapere se una stringa non è definita in uno script della shell Bash


151

Se voglio verificare la stringa nulla lo farei

[ -z $mystr ]

ma cosa succede se voglio verificare se la variabile è stata definita affatto? O non c'è distinzione negli script di Bash?


27
per favore, abbi l'abitudine di usare [-z "$ mystr"] anziché [-z $ mystr]
Charles Duffy,

come fare la cosa inversa? Intendo quando la stringa non è nulla
Apri la strada il

1
@flow: Che dire[ -n "${VAR+x}"] && echo not null
Lekensteyn,

1
@CharlesDuffy L'ho già letto in molte risorse online. Perché è preferito?
Sfogliando il

6
@Ayos perché se non hai virgolette, il contenuto della variabile è suddiviso in stringhe e globbed; se è una stringa vuota, diventa [ -z ]invece di [ -z "" ]; se ha spazi, diventa [ -z "my" "test" ]invece di [ -z my test ]; e se lo è [ -z * ], allora *viene sostituito con i nomi dei file nella tua directory.
Charles Duffy,

Risposte:


138

Penso che la risposta che cercate è implicito (se non dichiarato) di Vinko 's risposta , anche se non è scritto semplicemente. Per distinguere se VAR è impostato ma vuoto o no, è possibile utilizzare:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Probabilmente puoi combinare i due test sulla seconda riga in uno con:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Tuttavia, se leggi la documentazione di Autoconf, scoprirai che non raccomandano di combinare i termini con " -a" e raccomandano di usare test semplici separati combinati con &&. Non ho riscontrato un sistema in cui si è verificato un problema; ciò non significa che non esistessero (ma probabilmente sono estremamente rari in questi giorni, anche se non erano così rari in un lontano passato).

Puoi trovare i dettagli di questi, e altre relative espansioni dei parametri della shell , il comando testo[ ed espressioni condizionali nel manuale di Bash.


Recentemente mi è stato chiesto via e-mail su questa risposta con la domanda:

Usi due test e capisco bene il secondo, ma non il primo. Più precisamente non capisco la necessità di un'espansione variabile

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

Ciò non compirebbe lo stesso?

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

Domanda equa: la risposta è "No, la tua alternativa più semplice non fa la stessa cosa".

Supponiamo che scrivo questo prima del test:

VAR=

Il tuo test dirà "VAR non è impostato affatto", ma il mio dirà (implicitamente perché non riecheggia nulla) "VAR è impostato ma il suo valore potrebbe essere vuoto". Prova questo script:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

L'output è:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

Nella seconda coppia di test, la variabile è impostata, ma è impostata sul valore vuoto. Questa è la distinzione che fanno le notazioni ${VAR=value}e ${VAR:=value}. Idem per ${VAR-value}e ${VAR:-value}, e ${VAR+value}e ${VAR:+value}, e così via.


Come sottolinea Gili nella sua risposta , se corri bashcon l' set -o nounsetopzione, allora la risposta di base sopra fallisce unbound variable. È facilmente risolto:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Oppure potresti annullare l' set -o nounsetopzione con set +u( set -uequivalente a set -o nounset).


7
L'unico problema che ho con questa risposta è che svolge il suo compito in modo piuttosto indiretto e poco chiaro.
Svizzera

3
Per coloro che desiderano cercare la descrizione di ciò che significa sopra nella pagina man di bash, cercare la sezione "Espansione dei parametri" e quindi questo testo: "Quando non si esegue l'espansione della sottostringa, utilizzando i moduli documentati di seguito, bash test per un parametro non impostato o nullo ['null' che significa la stringa vuota]. L'omissione dei due punti comporta un test solo per un parametro non impostato (...) $ {parametro: + parola}: Usa valore alternativo. Se parametro è nullo o non impostato, non viene sostituito nulla, altrimenti viene sostituita l'espansione della parola. "
David Tonhofer,

2
@Swiss Questo non è né poco chiaro né indiretto, ma idiomatico. Forse per un programmatore che non ha familiarità ${+}ed ${-}è poco chiaro, ma la familiarità con questi costrutti è essenziale se si vuole essere un utente competente della shell.
William Pursell,

Vedere stackoverflow.com/a/20003892/14731 se si desidera implementarlo con set -o nounsetabilitato.
Gili,

Ho fatto molti test su questo; perché i risultati nei miei script erano incoerenti. Suggerisco alla gente di guardare [ -v VAR ]all'approccio " " con bash -s v4.2 e oltre .
sarà il

38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z funziona anche per variabili non definite. Per distinguere tra un indefinito e uno definito useresti le cose elencate qui o, con spiegazioni più chiare, qui .

Il modo più pulito è usare l'espansione come in questi esempi. Per ottenere tutte le opzioni, consultare la sezione Espansione dei parametri del manuale.

Parola alternativa:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Valore predefinito:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

Ovviamente useresti uno di questi in modo diverso, mettendo il valore desiderato al posto di "valore predefinito" e usando direttamente l'espansione, se appropriato.


1
Ma voglio distinguere se la stringa è "" o non è mai stata definita. È possibile?
Setjmp,

1
questa risposta non dice come distinguere tra questi due casi; segui il link bash faq che fornisce ulteriori discussioni.
Charles Duffy,

1
Ho aggiunto che dopo il suo commento, Charles
Vinko Vrsalovic il

2
Cerca "Parameter Expansion" nella pagina man di bash per tutti questi "trucchi". Ad esempio $ {foo: -default} per utilizzare un valore predefinito, $ {foo: = default} per assegnare il valore predefinito, $ {foo:? Messaggio di errore} per visualizzare un messaggio di errore se foo non è impostato, ecc.
Jouni K Seppänen,

18

Guida allo scripting bash avanzata, 10.2. Sostituzione parametri:

  • $ {var + blahblah}: se var è definito, 'blahblah' viene sostituito dall'espressione, altrimenti null viene sostituito
  • $ {var-blahblah}: se var è definito, è esso stesso sostituito, altrimenti 'blahblah' è sostituito
  • $ {var? blahblah}: se var è definito, viene sostituito, altrimenti la funzione esiste con 'blahblah' come messaggio di errore.


per basare la logica del programma sul fatto che la variabile $ mystr sia definita o meno, è possibile effettuare le seguenti operazioni:

isdefined=0
${mystr+ export isdefined=1}

ora, se isdefined = 0 allora la variabile non era definita, se isdefined = 1 la variabile era definita

Questo modo di controllare le variabili è migliore della risposta di cui sopra perché è più elegante, leggibile e se la shell bash è stata configurata per errori nell'uso di variabili non definite (set -u), lo script terminerà prematuramente.


Altre cose utili:

avere un valore predefinito di 7 assegnato a $ mystr se non definito, e lasciare intatto altrimenti:

mystr=${mystr- 7}

per stampare un messaggio di errore ed uscire dalla funzione se la variabile non è definita:

: ${mystr? not defined}

Fai attenzione qui che ho usato ':' per non avere il contenuto di $ mystr eseguito come comando nel caso in cui sia definito.


Non sapevo del? sintassi per variabili BASH. È una bella sorpresa.
Svizzero

Funziona anche con riferimenti a variabili indirette. Questo passa: var= ; varname=var ; : ${!varname?not defined}e questo termina: varname=var ; : ${!varname?not defined}. Ma è una buona abitudine usare set -uche fa lo stesso molto più facile.
ceving

11

Un riepilogo dei test.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"

Buone pratiche a bash. A volte i percorsi dei file hanno spazi o per proteggere dall'iniezione di comandi
k107

1
Penso che ${var+x}non sia necessario tranne se i nomi delle variabili possono contenere spazi.
Anne van Rossum,

Non solo buone pratiche; è richiesto con [per ottenere risultati corretti. Se varnon è impostato o è vuoto, si [ -n $var ]riduce a [ -n ]quale ha lo stato di uscita 0, mentre [ -n "$var" ]ha lo stato di uscita previsto (e corretto) di 1.
chepner

5

https://stackoverflow.com/a/9824943/14731 contiene una risposta migliore (una più leggibile e set -o nounsetcompatibile con abilitato). Funziona più o meno così:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi

4

Il modo esplicito per verificare la presenza di una variabile definita sarebbe:

[ -v mystr ]

1
Non riesci a trovarlo in nessuna delle pagine man (per bash o test) ma funziona per me. Hai idea di quali versioni sono state aggiunte?
Ash Berlin-Taylor,

1
È documentato in Espressioni condizionali , a cui fa riferimento l' testoperatore nella parte finale della sezione Bourne Shell Builtins . Non so quando è stato aggiunto, ma non è nella versione di Apple bash(basata su bash3.2.51), quindi è probabilmente una funzione 4.x.
Jonathan Leffler,

1
Questo non funziona con me GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). L'errore è -bash: [: -v: unary operator expected. Per un commento qui , richiede almeno bash 4.2 per funzionare.
Acumenus,

Grazie per aver verificato quando è diventato disponibile. L'avevo usato prima su vari sistemi, ma all'improvviso non ha funzionato. Sono venuto qui per curiosità e sì, ovviamente la bash su questo sistema è una bash 3.x.
clacke,

1

un'altra opzione: l'espansione "elenca gli indici di array" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

l'unica volta che questo si espande nella stringa vuota è quando foonon è impostato, quindi puoi controllarlo con la stringa condizionale:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

dovrebbe essere disponibile in qualsiasi versione bash> = 3.0


1

non perdere ancora questa bici, ma volevo aggiungere

shopt -s -o nounset

è qualcosa che potresti aggiungere all'inizio di uno script, il che causerà un errore se le variabili non sono dichiarate in nessun punto dello script. Il messaggio che vedresti è unbound variable, ma come altri menzionano non catturerà una stringa vuota o un valore nullo. Per assicurarci che ogni singolo valore non sia vuoto, possiamo testare una variabile man mano che viene espansa ${mystr:?}, nota anche come espansione del simbolo del dollaro, con la quale si commetterebbe un errore parameter null or not set.


1

Il Manuale di riferimento di Bash è una fonte autorevole di informazioni su bash.

Ecco un esempio di test di una variabile per vedere se esiste:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(Dalla sezione 6.3.2 .)

Si noti che lo spazio bianco dopo l'apertura [e prima del non] è opzionale .


Suggerimenti per gli utenti di Vim

Avevo una sceneggiatura che aveva diverse dichiarazioni come segue:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

Ma volevo che rimandassero a qualsiasi valore esistente. Quindi li ho riscritti per assomigliare a questo:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

Sono stato in grado di automatizzare questo in vim usando una regex veloce:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

Questo può essere applicato selezionando visivamente le linee pertinenti, quindi digitando :. La barra dei comandi viene pre-popolata con :'<,'>. Incolla il comando sopra e premi invio.


Testato su questa versione di Vim:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Gli utenti di Windows potrebbero desiderare terminazioni di linea diverse.


0

Ecco quello che penso sia un modo molto più chiaro per verificare se una variabile è definita:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Usalo come segue:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi

1
Scrivere una funzione non è una cattiva idea; invocare grepè terribile. Si noti che bashsupporta ${!var_name}come un modo per ottenere il valore di una variabile il cui nome è specificato in $var_name, quindi name=value; value=1; echo ${name} ${!name}produce value 1.
Jonathan Leffler,

0

Una versione più breve per testare una variabile non definita può essere semplicemente:

test -z ${mystr} && echo "mystr is not defined"

-3

call set senza alcun argomento .. genera tutti i var definiti ..
gli ultimi nell'elenco sarebbero quelli definiti nel tuo script ..
quindi potresti indirizzare il suo output a qualcosa che possa capire quali cose sono definite e cosa no


2
Non ho votato a favore, ma è una specie di mazza rompere un dado piuttosto piccolo.
Jonathan Leffler,

In realtà mi piace questa risposta meglio della risposta accettata. È chiaro cosa si sta facendo in questa risposta. La risposta accettata è traballante da morire.
Svizzero

Sembra che questa risposta sia più un effetto collaterale dell'implementazione di "set" che qualcosa di desiderabile.
Jonathan Morgan,
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.