Perché devo citare la variabile per if, ma non per l'eco?


26

Ho letto che hai bisogno di virgolette doppie per espandere le variabili, ad es

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

funzionerà come previsto, mentre

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

dirà sempre $test okanche se $testè nullo.

ma allora perché non abbiamo bisogno di virgolette echo $test?


2
Se non citate una variabile da utilizzare come arg echo, gli spazi extra e le nuove righe verrebbero rimossi.
Giordania,

Risposte:


36

Hai sempre bisogno di virgolette attorno alle variabili in tutti i contesti di elenco , ovvero ovunque la variabile possa essere espansa su più valori a meno che tu non voglia i 3 effetti collaterali di lasciare una variabile non quotata.

i contesti di lista includono argomenti per comandi semplici come [o echo, i for i in <here>, assegnazioni ad array ... Ci sono altri contesti in cui è necessario anche citare le variabili. La cosa migliore è citare sempre le variabili a meno che tu non abbia una buona ragione per non farlo.

Pensa all'assenza di virgolette (nei contesti di elenco) come l' operatore split + glob .

Come se lo echo $testfosse echo glob(split("$test")).

Il comportamento della shell è confuso per la maggior parte delle persone perché nella maggior parte delle altre lingue, metti le virgolette attorno a stringhe fisse, come puts("foo")e non attorno a variabili (come puts(var)) mentre nella shell è il contrario: tutto è stringa nella shell, quindi metti le virgolette intorno a tutto sarebbe ingombrante, tu echo test, non è necessario "echo" "test". Nella shell, le virgolette sono usate per qualcos'altro: prevenire qualche significato speciale di alcuni personaggi e / o influenzare il comportamento di alcune espansioni.

In [ -n $test ]o echo $test, la shell si dividerà $test(sugli spazi vuoti per impostazione predefinita), quindi eseguirà la generazione del nome del file (espandi tutti i *modelli, "?" ... all'elenco dei file corrispondenti), quindi passerà tale elenco di argomenti ai comandi [o echo.

Ancora una volta, pensalo come "[" "-n" glob(split("$test")) "]". Se $testè vuoto o contiene solo spazi vuoti (spc, tab, nl), l'operatore split + glob restituirà un elenco vuoto, quindi lo [ -n $test ]sarà "[" "-n" "]", che è un test per verificare se "-n" è la stringa vuota o meno. Ma immagina cosa sarebbe successo se $testfosse "*" o "= pippo" ...

In [ -n "$test" ], [è passato i quattro argomenti "[", "-n", ""e "]"(senza le virgolette), che è quello che vogliamo.

Che sia echoo [non faccia alcuna differenza, è solo che echogenera la stessa cosa, indipendentemente dal fatto che abbia passato un argomento vuoto o nessun argomento.

Vedi anche questa risposta a una domanda simile per maggiori dettagli sul [comando e sul [[...]]costrutto.


7

La risposta di @h3rrmiller è utile per spiegare perché hai bisogno delle virgolette per if(o meglio, [/ test), ma direi che la tua domanda non è corretta.

Prova i seguenti comandi e vedrai cosa intendo.

export testvar="123    456"
echo $testvar
echo "$testvar"

Senza virgolette, la sostituzione della variabile fa espandere il secondo comando a:

echo 123    456

e gli spazi multipli vengono compressi in uno solo:

echo 123 456

Con le virgolette, gli spazi vengono conservati.

Questo accade perché quando si cita un parametro (se tale parametro viene passato a echo, testo qualche altro comando), il valore di quel parametro viene inviato come un valore al comando. Se non lo citate, la shell fa la sua normale magia di cercare spazi bianchi per determinare dove ogni parametro inizia e finisce.

Ciò può anche essere illustrato dal seguente programma C (molto molto semplice). Prova quanto segue sulla riga di comando (potresti volerlo fare in una directory vuota in modo da non rischiare di sovrascrivere qualcosa).

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

e poi...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

Dopo l'esecuzione paramtest, $?manterrà il numero di parametri che è stato passato (e quel numero verrà stampato).


2

Questo è tutto su come la shell interpreta la linea prima che un programma venga eseguito.

Se la riga legge echo I am $USER, la shell la espande echo I am blrfle echonon ha idea se l'origine del testo sia un'espansione letterale o variabile. Allo stesso modo, se una riga legge echo I am $UNDEFINED, la shell si espanderà $UNDEFINEDnel nulla e gli argomenti dell'eco saranno I am, e questa è la fine. Dal momento che echofunziona bene senza argomenti, echo $UNDEFINEDè completamente valido.

Il tuo problema con if non è proprio con if, perché ifesegue semplicemente qualunque programma e argomenti lo seguano ed esegue la thenparte se il programma esce 0(o la elseparte se ce n'è una e il programma esce non- 0):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

Quando usi if [ ... ]un confronto, non stai usando le primitive integrate nella shell. In realtà stai istruendo la shell per eseguire un programma chiamato [che è un leggerissimo superset test(1)che richiede il suo ultimo argomento ]. Entrambi i programmi si chiudono 0se la condizione del test risulta vera e in 1caso contrario.

Il motivo per cui alcuni test si interrompono quando una variabile non è definita è perché test non vede che stai usando una variabile. Ergo, si [ $UNDEFINED -eq 2 ]interrompe perché quando la shell ha finito con esso, tutto testvede per argomenti -eq 2 ], che non è un test valido. Se lo facessi con qualcosa di definito, come [ $DEFINED -ne 0 ], funzionerebbe perché la shell lo espanderebbe in un test valido (ad es 0 -ne 0.).

C'è una differenza semantica tra foo $UNDEFINED bar, che si espande in due argomenti ( fooe bar) perché $UNDEFINEDall'altezza del suo nome. Confronta questo con foo "$UNDEFINED" bar, che si espande a tre argomenti ( foo, una stringa vuota e una barra). Le citazioni costringono la shell a interpretarle come un argomento, indipendentemente dal fatto che ci sia qualcosa tra loro o no.


0

Senza virgolette $test potrebbe espandersi per essere più di una parola, quindi deve essere citato per non interrompere la sintassi poiché ogni interruttore all'interno del [comando si aspetta un argomento che è quello che fanno le virgolette (trasforma tutto ciò che si $testespande in un argomento)

Il motivo per cui non hai bisogno di virgolette per espandere una variabile echo è perché non si aspetta un argomento. Stamperà semplicemente ciò a cui gli dici. Quindi, anche se si $testespande a 100 parole, l'eco continuerà a stamparlo.

Dai un'occhiata a Bash Pitfalls


si ma perché non ne abbiamo bisogno echo?
CharlesB,

@CharlesB Hai bisogno delle virgolette per echo. Cosa ti fa pensare diversamente?
Gilles 'SO- smetti di essere malvagio' il

Non ne ho bisogno, posso echo $teste funziona (produce il valore di $ test)
CharlesB,

1
@CharlesB Emette il valore di $ test solo se non contiene spazi multipli ovunque. Prova il programma nella mia risposta per un'illustrazione del motivo.
un CVn del

0

I parametri vuoti vengono rimossi se non citati:

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

Il comando chiamato non vede che c'era un parametro vuoto sulla riga di comando della shell. Sembra che [sia definito per restituire 0 per -n con nulla che segue. Perché mai.

La citazione fa la differenza anche per l'eco, in diversi casi:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"

2
Non è echo, è il guscio. Vedresti lo stesso comportamento con ls. Prova un touch '*'po 'di tempo se ti senti avventuroso. :)
un CVn del

Questa è solo una formulazione in quanto non vi è alcuna differenza nel caso 'if [...] `. [non è un comando shell speciale. È diverso da [[(in bash) dove la quotazione non è necessaria.
Hauke ​​Laging,
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.