Warum muss ich eine Variable für if angeben, aber nicht für echo?

26

Ich habe gelesen, dass Sie doppelte Anführungszeichen zum Erweitern von Variablen benötigen, z

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

wird wie erwartet funktionieren, während

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

wird immer sagen, $test okauch wenn $testnull ist.

aber warum brauchen wir dann keine Anführungszeichen echo $test?

CharlesB
quelle
2
Wenn Sie keine Variable echoangeben, die als Argument verwendet werden soll, werden zusätzliche Leerzeichen und Zeilenumbrüche entfernt.
Jordan

Antworten:

36

Sie immer Anführungszeichen um Variablen in allen brauchen Liste Kontexten, die überall die Variable kann auf mehrere Werte erweitert werden , wenn Sie die 3 Nebenwirkungen des Ausscheidens eine variable unquoted wollen.

Liste Kontexte umfassen Argumente einfache Befehle wie [oder echo, die for i in <here>Zuweisungen zu Arrays ... Es gibt andere Kontexte , in denen Variablen auch zitiert werden müssen. Am besten zitieren Sie Variablen immer, es sei denn, Sie haben einen guten Grund, dies nicht zu tun.

Stellen Sie sich das Fehlen von Anführungszeichen (in Listenkontexten) als den Operator split + glob vor .

Als ob echo $testwar echo glob(split("$test")).

Das Shell-Verhalten ist für die meisten Leute verwirrend, weil Sie in den meisten anderen Sprachen feste Zeichenfolgen in Anführungszeichen setzen puts("foo")und nicht Variablen (wie puts(var)), während es in der Shell umgekehrt ist: Alles ist Zeichenfolge in der Shell, also Anführungszeichen um alles wäre umständlich, du echo testmusst nicht "echo" "test". In der Shell werden Anführungszeichen für etwas anderes verwendet: Verhindern Sie eine spezielle Bedeutung einiger Zeichen und / oder beeinflussen Sie das Verhalten einiger Erweiterungen.

In [ -n $test ]oder echo $testwird die Shell geteilt $test(standardmäßig in Leerzeichen) und anschließend die Dateinamengenerierung ausgeführt (alle *Muster '?' ... in die Liste der übereinstimmenden Dateien einblenden) und diese Liste der Argumente an die Befehle [oder übergeben echo.

Betrachten Sie es erneut als "[" "-n" glob(split("$test")) "]". Wenn $testleer oder enthält nur Leerzeichen (spc, Tab, nl), dann wird die Split + glob Operator eine leere Liste zurück, so das [ -n $test ]sein wird "[" "-n" "]", das ist ein Test, um zu überprüfen wheter „-n“ die leere Zeichenkette ist oder nicht. Aber stellen Sie sich vor, was passiert wäre, wenn $test"*" oder "= foo" wäre ...

In [ -n "$test" ], [wird die vier Argumente übergeben "[", "-n", ""und "]"(ohne die Anführungszeichen), das ist , was wir wollen.

Ob es echooder [keinen Unterschied macht, dann ist es nur , dass echoAusgänge die gleiche Sache , ob es ein leeres Argument oder kein Argument überhaupt übergeben wird .

Siehe auch diese Antwort auf eine ähnliche Frage für weitere Details zum [Befehl und zum [[...]]Konstrukt.

Stéphane Chazelas
quelle
7

@ h3rrmillers Antwort ist gut, um zu erklären, warum Sie die Anführungszeichen für if(oder besser gesagt, [/ test) benötigen , aber ich würde tatsächlich annehmen, dass Ihre Frage falsch ist.

Probieren Sie die folgenden Befehle aus, und Sie werden sehen, was ich meine.

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

Ohne die Anführungszeichen bewirkt die Variablensubstitution, dass der zweite Befehl wie folgt erweitert wird:

echo 123    456

und die mehreren Leerzeichen werden zu einem einzigen zusammengefasst:

echo 123 456

Bei den Anführungszeichen bleiben die Leerzeichen erhalten.

Dies geschieht , weil , wenn Sie einen Parameter zitieren (ob diese Parameter übergeben wird echo, testoder ein anderer Befehl), wird der Wert dieses Parameters als gesendet wird , einen Wert an den Befehl. Wenn Sie es nicht zitieren, sucht die Shell wie gewohnt nach Leerzeichen, um zu bestimmen, wo die einzelnen Parameter beginnen und enden.

Dies kann auch durch das folgende (sehr sehr einfache) C-Programm veranschaulicht werden. Versuchen Sie Folgendes in der Befehlszeile (möglicherweise möchten Sie dies in einem leeren Verzeichnis tun, um nicht zu riskieren, etwas zu überschreiben).

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

und dann...

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

Nach dem Laufen paramtest, $?wird die Anzahl der Parameter halten , wurde übergeben (und diese Zahl wird gedruckt).

ein CVn
quelle
2

Hier geht es darum, wie die Shell die Zeile interpretiert, bevor ein Programm ausgeführt wird.

Wenn die Zeile lautet echo I am $USER, erweitert die Shell sie echo I am blrflund echohat keine Ahnung, ob der Ursprung des Texts ein Literal oder eine variable Erweiterung ist. In ähnlicher Weise wird echo I am $UNDEFINEDdie Shell beim Lesen einer Zeile $UNDEFINEDin nichts expandieren und die Argumente von echo werden I am, und das ist das Ende davon. Da echofunktioniert ganz gut ohne Argumente, echo $UNDEFINEDist völlig gültig.

Ihr Problem mit ifist nicht wirklich mit if, da ifnur das darauf folgende Programm und die darauf folgenden Argumente ausgeführt werden und der thenTeil ausgeführt wird, wenn das Programm beendet wird 0(oder der elseTeil, wenn einer vorhanden ist und das Programm nicht beendet wird 0):

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

Wenn Sie if [ ... ]einen Vergleich durchführen, verwenden Sie keine in die Shell eingebauten Grundelemente. Sie weisen die Shell tatsächlich an, ein Programm auszuführen, das aufgerufen [wird. Dies ist eine sehr geringfügige Obermenge test(1)dessen letztes Argument sein muss ]. Beide Programme werden beendet, 0wenn die Testbedingung erfüllt ist und 1nicht.

Der Grund, warum einige Tests abgebrochen werden, wenn eine Variable undefiniert ist, testliegt darin, dass Sie nicht erkennen, dass Sie eine Variable verwenden. Ergo, [ $UNDEFINED -eq 2 ]bricht ab, weil bis die Shell fertig ist, alle testnach Argumenten suchen -eq 2 ], was kein gültiger Test ist. Wenn Sie dies mit einer definierten Methode tun [ $DEFINED -ne 0 ]würden , wie z. B. , würde dies funktionieren, da die Shell es zu einem gültigen Test (z 0 -ne 0. B. ) erweitern würde.

Es gibt einen semantischen Unterschied zwischen foo $UNDEFINED bar, der sich zu zwei Argumenten ( foound bar) erweitert, weil $UNDEFINEDer seinem Namen gerecht wurde. Vergleichen Sie dies mit foo "$UNDEFINED" bar, was sich auf drei Argumente ( fooeine leere Zeichenkette und einen Balken) erweitert. Anführungszeichen zwingen die Shell, sie als Argument zu interpretieren, ob sich etwas zwischen ihnen befindet oder nicht.

Blrfl
quelle
0

Ohne Anführungszeichen $testkann es zu mehr als einem Wort kommen, daher muss es in Anführungszeichen gesetzt werden, um die Syntax nicht zu brechen, da jeder Schalter innerhalb des [Befehls ein Argument erwartet, was die Anführungszeichen bewirken (alles wird $testzu einem Argument erweitert).

Der Grund, warum Sie keine Anführungszeichen benötigen, um eine Variable mit zu erweitern, echoist, dass sie kein einziges Argument erwartet. Es wird einfach gedruckt, was Sie ihm sagen. Selbst wenn $testdas Echo auf 100 Wörter erweitert wird, wird es trotzdem gedruckt.

Schauen Sie sich Bash Pitfalls an

h3rrmiller
quelle
Ja, aber warum brauchen wir es nicht echo?
CharlesB
@CharlesB Sie benötigen die Anführungszeichen für echo. Was lässt dich anders denken?
Gilles 'SO- hör auf böse zu sein'
Ich brauche sie nicht, ich kann echo $testund es funktioniert (es gibt den Wert von $ test)
CharlesB
1
@CharlesB Gibt nur den Wert von $ test aus, wenn dieser nirgendwo mehrere Leerzeichen enthält. Versuchen Sie das Programm in meiner Antwort, um den Grund zu veranschaulichen.
ein Lebenslauf
0

Leere Parameter werden entfernt, wenn sie nicht in Anführungszeichen gesetzt werden:

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

Der aufgerufene Befehl erkennt nicht, dass in der Shell-Befehlszeile ein leerer Parameter vorhanden ist. Es scheint, dass [definiert ist, um 0 für -n zurückzugeben, ohne dass etwas folgt. Warum auch immer.

Zitate wirken sich in einigen Fällen auch auf das Echo aus:

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

var="foo        bar"
echo $var
echo "$var"
Hauke ​​Laging
quelle
2
Es ist nicht echodie Muschel. Sie würden das gleiche Verhalten mit sehen ls. Probieren touch '*'Sie es aus, wenn Sie sich abenteuerlustig fühlen. :)
ein Lebenslauf
Das ist nur eine Formulierung, da es keinen Unterschied zum "wenn" -Fall gibt. [ist kein spezieller Shell-Befehl. Das ist etwas anderes als [[(in Bash)], wo kein Zitieren erforderlich ist.
Hauke ​​Laging