Perché i confronti degli script di shell spesso usano x $ VAR = xyes?

Lo vedo spesso negli script di build dei progetti che usano autotools (autoconf, automake). Quando qualcuno vuole controllare il valore di una variabile di shell, usa frequentemente questo idioma:

if test "x$SHELL_VAR" = "xyes"; then ... 

Qual è il vantaggio di questo semplicemente controllando il valore in questo modo:

 if test $SHELL_VAR = "yes"; then ... 

Immagino ci debba essere una ragione per cui lo vedo così spesso, ma non riesco a capire di cosa si tratta.

Se si sta utilizzando una shell che esegue una semplice sostituzione e la variabile SHELL_VAR non esiste (o è vuota), è necessario SHELL_VAR attenzione ai casi limite. Le seguenti traduzioni avverranno:

 if test $SHELL_VAR = yes; then --> if test = yes; then if test x$SHELL_VAR = xyes; then --> if test x = xyes; then 

Il primo di questi genererà un errore dal momento che l’argomento del primo test è scomparso. Il secondo non ha questo problema.

Il tuo caso si traduce come segue:

 if test "x$SHELL_VAR" = "xyes"; then --> if test "x" = "xyes"; then 

Può sembrare un po ‘ridondante dal momento che ha sia le virgolette che la “x”, ma gestirà anche una variabile con spazi al suo interno, senza darla come due argomenti al comando test .

L’altro motivo (diverso dalle variabili vuote) ha a che fare con l’elaborazione delle opzioni. Se scrivi:

 if test "$1" = "abc" ; then ... 

e $1 ha il valore -n o -z o qualsiasi altra opzione valida per il comando test , la syntax è ambigua. La x nella parte anteriore impedisce che un trattino principale venga prelevato come opzione da test .

Tieni presente che questo dipende dalla shell. Alcune shell ( csh per una, credo) si lamenteranno amaramente se la variabile di ambiente non esiste piuttosto che restituire una stringa vuota).

L’altra ragione che nessun altro ha ancora menzionato riguarda l’elaborazione delle opzioni. Se scrivi:

 if [ "$1" = "abc" ]; then ... 

e $ 1 ha il valore ‘-n’, la syntax del comando test è ambigua; non è chiaro cosa stavi testando. La ‘x’ nella parte anteriore impedisce a un trattino principale di causare problemi.

Devi cercare shell molto antiche per trovarne una dove il comando test non ha supporto per -n o -z ; il comando di test della versione 7 (1978) li includeva. Non è del tutto irrilevante: alcuni file UNIX della Versione 6 sono sfuggiti a BSD, ma al giorno d’oggi, sarebbe estremamente difficile trovare qualcosa di così antico nell’uso corrente.

Non usare le virgolette sui valori è pericoloso, come hanno sottolineato altre persone. Infatti, se esiste la possibilità che i nomi dei file possano contenere spazi (MacOS X e Windows lo incoraggiano entrambi, e Unix lo ha sempre supportato, sebbene strumenti come xargs rendano più difficile), allora dovresti racchiudere i nomi dei file tra virgolette ogni volta che li usi anche tu. A meno che tu non sia il responsabile del valore (ad esempio durante la gestione delle opzioni, e imposta la variabile su “no” all’avvio e “sì” quando un flag è incluso nella riga di comando) non è sicuro usare forms non quotate di variabili fino a quando non li avrai dimostrati al sicuro – e potresti anche farlo tutto il tempo per molti scopi. Oppure documenta che i tuoi script falliranno in modo orribile se gli utenti tentano di elaborare file con spazi vuoti nei nomi. (E ci sono anche altri personaggi di cui preoccuparsi – anche i backtick potrebbero essere piuttosto cattivi, per esempio.)

Ci sono due ragioni per cui so di questa convenzione:

http://tldp.org/LDP/abs/html/comparison-ops.html

In un test composto, anche quotare la variabile stringa potrebbe non essere sufficiente. [-n “$ string” -o “$ a” = “$ b”] potrebbe causare un errore con alcune versioni di Bash se $ string è vuoto. Il modo sicuro è di aggiungere un carattere in più alle variabili eventualmente vuote, [“x $ stringa”! = X -o “x $ a” = “x $ b”] (le “x’s” cancellano).

In secondo luogo, in altri shell di Bash, specialmente quelli più vecchi, le condizioni di test come ‘-z’ per testare una variabile vuota non esistevano, quindi mentre questo:

 if [ -z "$SOME_VAR" ]; then echo "this variable is not defined" fi 

funzionerà bene in BASH, se stai mirando alla portabilità in vari ambienti UNIX dove non puoi essere sicuro che la shell di default sarà Bash e se supporta la condizione -z test, è più sicuro usare il modulo se [” x $ SOME_VAR “=” x “] dal momento che avrà sempre l’effetto desiderato. Essenzialmente si tratta di un vecchio trucco di shell scripting per trovare una variabile vuota, ed è ancora usato oggi per compatibilità all’indietro nonostante siano disponibili metodi più puliti.

Raccomando invece:

 if test "yes" = "$SHELL_VAR"; then 

dal momento che elimina la brutta x , e risolve ancora il problema menzionato da https://stackoverflow.com/a/174288/895245 con cui $SHELL_VAR può iniziare - e può essere letto come opzione.

Credo che sia dovuto a

 SHELLVAR=$(true) if test $SHELLVAR = "yes" ; then echo "yep" ; fi # bash: test: =: unary operator expected 

così come

 if test $UNDEFINEDED = "yes" ; then echo "yep" ; fi # bash: test: =: unary operator expected 

e

 SHELLVAR=" hello" if test $SHELLVAR = "hello" ; then echo "yep" ; fi # yep 

tuttavia, di solito dovrebbe funzionare

 SHELLVAR=" hello" if test "$SHELLVAR" = "hello" ; then echo "yep" ; fi # 

ma quando si lamenta in uscita da qualche altra parte, è difficile dire che cosa si lamenti di suppongo, quindi

 SHELLVAR=" hello" if test "x$SHELLVAR" = "xhello" ; then echo "yep" ; fi 

funziona altrettanto bene, ma sarebbe più facile eseguire il debug.

Lo facevo in DOS quando SHELL_VAR poteva essere indefinito.

Se non si fa la cosa “x $ SHELL_VAR”, se $ SHELL_VAR non è definito, si ottiene un errore sul fatto che “=” non è un operatore monadico o qualcosa del genere.