Warum funktioniert die Parametererweiterung mit Leerzeichen ohne Anführungszeichen in doppelten Klammern "[[", aber nicht in einfachen Klammern "["?

85

Ich bin verwirrt mit einfachen oder doppelten Klammern. Schau dir diesen Code an:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

Es funktioniert perfekt, obwohl die Zeichenfolge ein Leerzeichen enthält. Aber wenn ich es in einzelne Klammer ändere:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

Es sagt:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

Wenn ich es ändere zu:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

Es funktioniert gut. Kann jemand erklären, was passiert? Wann sollte ich Variablen "${var}"in doppelte Anführungszeichen setzen , um Probleme durch Leerzeichen zu vermeiden?

Majid Azimi
quelle
8
Bash FAQ 31
jw013
Eine allgemeinere vs Frage: unix.stackexchange.com/questions/306111/…
Ciro Santilli

Antworten:

83

Die einzelne Klammer [ist eigentlich ein Alias ​​für den testBefehl, es ist keine Syntax.

Einer der Nachteile (von vielen) der einzelnen Klammern ist, dass, wenn einer oder mehrere der Operanden, die ausgewertet werden sollen, eine leere Zeichenfolge zurückgeben, beanstandet wird, dass zwei Operanden (binär) erwartet wurden. Dies ist der Grund, warum Sie Leute sehen [ x$foo = x$blah ], die xgarantieren, dass der Operand niemals eine leere Zeichenkette ergibt .

Die doppelte Klammer [[ ]]hingegen ist syntaktisch und weitaus leistungsfähiger als [ ]. Wie Sie herausgefunden haben, gibt es kein einzelnes Operandenproblem und es ermöglicht auch eine C-ähnliche Syntax mit >, <, >=, <=, !=, ==, &&, ||Operatoren.

Meine Empfehlung lautet: Wenn Ihr Dolmetscher ist #!/bin/bash, dann immer verwenden[[ ]]

Es ist wichtig zu beachten, dass dies [[ ]]nicht von allen POSIX-Shells unterstützt wird, jedoch von vielen Shells wie zshund kshzusätzlich unterstützt wirdbash

SiegeX
quelle
16
Das Problem mit leeren Zeichenfolgen (und vielen anderen) wird durch die Verwendung von Anführungszeichen gelöst. Das "x" soll eine andere Art von Problemen abdecken: Operanden können als Operatoren verwendet werden . Wie wenn $fooist !oder (oder -n... Dieses Problem ist nicht als Problem mit POSIX-Shells gedacht, bei denen die Anzahl der Argumente (neben [und ]) nicht größer als vier ist.
Stéphane Chazelas
21
Zu klären, [ x$foo = x$blah ]ist genauso schlicht falsch wie [ $foo = $bar ]. [ "$foo" = "$bar" ]ist in jeder POSIX-kompatiblen Shell korrekt, [ "x$foo" = "x$bar" ]würde in jeder Bourne-ähnlichen Shell funktionieren, aber das xist nicht für Fälle, in denen Sie leere Zeichenfolgen haben, sondern wo $foosein !oder -n...
Stéphane Chazelas
1
Interessante Semantik in dieser Antwort. Ich bin nicht sicher, was Sie damit meinen, dass es keine Syntax ist . Natürlich [ist nicht genau ein Pseudonym für test. Wenn dies der Fall testwäre, würde eine schließende eckige Klammer akzeptiert. test -n foo ]. Tut es testaber nicht und [benötigt eines. [ist testin jeder Hinsicht identisch mit , aber so aliasfunktioniert das nicht . Bash beschreibt [und testals Shell-Builtins , aber [[als Shell-Schlüsselwort .
Kojiro
1
Nun, in bash [ist eine Shell eingebaut, aber / usr / bin / [ist auch eine ausführbare Datei. Es ist traditionell eine Verknüpfung zu / usr / bin / test, aber in modernen Gnu-Coreutils handelt es sich um eine separate Binärdatei. Die traditionelle Version von test untersucht argv [0], um festzustellen, ob es als [aufgerufen wird und sucht dann nach der Übereinstimmung].
Evan
Mein Bash funktioniert nicht mit >=und<=
Student
51

Der [Befehl ist ein gewöhnlicher Befehl. Obwohl die meisten Shells dies als integrierte Funktion für die Effizienz bereitstellen, werden die normalen syntaktischen Regeln der Shell eingehalten. [ist genau gleichbedeutend mit test, außer dass [a ]als letztes Argument benötigt wird und testnicht.

Die doppelten Klammern [[ … ]]sind spezielle Syntax. Sie wurden in ksh (einige Jahre später [) eingeführt, da [ihre korrekte Verwendung problematisch sein kann und [[einige neue nette Ergänzungen zulässt, die Shell-Sonderzeichen verwenden. Zum Beispiel können Sie schreiben

[[ $x = foo && $y = bar ]]

weil der gesamte Bedingungsausdruck von der Shell analysiert wird, während er [ $x = foo && $y = bar ]zuerst in zwei Befehle aufgeteilt [ $x = foound $y = bar ]vom &&Operator getrennt wird . In ähnlicher Weise ermöglichen doppelte Klammern Dinge wie die Pattern-Matching-Syntax, um z. B. [[ $x == a* ]]zu testen, ob der Wert von mit xbeginnt a; In einfachen Klammern wird dies a*auf die Liste der Dateien ausgedehnt, deren Namen aim aktuellen Verzeichnis beginnen. Doppelte Klammern wurden zuerst in ksh eingeführt und sind nur in ksh, bash und zsh verfügbar.

In einfachen Klammern müssen Sie Variablen wie an den meisten anderen Stellen in doppelte Anführungszeichen setzen, da es sich nur um Argumente für einen Befehl handelt (der zufällig der [Befehl ist). In doppelten Klammern brauchen Sie keine doppelten Anführungszeichen, da die Shell keine Worttrennung oder Globen ausführt: Sie analysiert einen bedingten Ausdruck, keinen Befehl.

Eine Ausnahme besteht jedoch darin, [[ $var1 = "$var2" ]]dass Sie die Anführungszeichen benötigen, wenn Sie einen Byte-zu-Byte-Vergleich von Zeichenfolgen durchführen $var2möchten $var1.

Eine Sache, mit der Sie nichts anfangen können, [[ … ]]ist die Verwendung einer Variablen als Operator. Dies ist beispielsweise vollkommen legal (aber selten nützlich):

if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi

if [ "$x" "$op" "$y" ]; then 

In deinem Beispiel

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then 

der Befehl innerhalb der ifist [mit den 4 Argumente -d, /home/mazimi/VirtualBox, VMsund ]. Die Shell analysiert -d /home/mazimi/VirtualBoxund weiß dann nicht, was zu tun ist VMs. Sie müssten verhindern, dass sich das Wort aufteilt ${dir}, um einen wohlgeformten Befehl zu erhalten.

Verwenden Sie generell immer doppelte Anführungszeichen um Variablen- und Befehlssubstitutionen, es sei denn, Sie wissen, dass Sie das Ergebnis aufteilen und glätten möchten. Die wichtigsten Stellen, an denen es sicher ist, keine doppelten Anführungszeichen zu verwenden, sind:

  • in einer Zuweisung: foo=$bar(aber beachten Sie, dass Sie die doppelten Anführungszeichen in export "foo=$bar"oder in Array-Zuweisungen wie benötigen array=("$a" "$b"));
  • in einer caseAussage case $foo in …:;
  • in doppelte eckige Klammern außer auf der rechten Seite des =oder ==Operator (es sei denn , Sie Pattern - Matching tun wollen): [[ $x = "$y" ]].

In all diesen Fällen ist es richtig, doppelte Anführungszeichen zu verwenden. Sie können also auch die erweiterten Regeln überspringen und die Anführungszeichen immer wieder verwenden.

Gilles
quelle
1
@StephaneChazelas Ein Fehler von mir. Es stellt sich heraus, dass [es sich in der Tat um System III aus dem Jahr 1981 handelt , ich dachte, es wäre älter.
Gilles
8

Wann sollte ich Variablen "${var}"in doppelte Anführungszeichen setzen , um Probleme durch Leerzeichen zu vermeiden?

Implizit in dieser Frage ist

Warum ist nicht gut genug?${variable_name}

${variable_name} bedeutet nicht, was Sie denken, dass es tut ...

... wenn Sie denken , es hat etwas mit Problemen durch Leerzeichen (in Variablenwerte) verursacht zu tun. ist gut dafür:${variable_name}

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

und sonst nichts! 1  macht nicht jeder gutwenn Sie sofort sind sie mit einem Zeichen nachdie Teil eines Variablenname sein könnte: ein Brief (-oder-), einen Unterstrich () oder eine Ziffer (-). Und selbst dann können Sie es umgehen:${variable_name}AZaz_09

$ echo "$bar"d
food

Ich versuche nicht, seine Verwendung zu entmutigen - echo "${bar}d"ist hier wahrscheinlich die beste Lösung -, sondern Menschen davon abzuhalten, sich auf Klammern anstatt auf Zitate zu verlassen oder Klammern instinktiv anzuwenden und dann zu fragen: „Brauche ich jetzt auch Zitate ? „  Sie sollten immer Anführungszeichen verwenden, es sei denn, Sie haben einen guten Grund, dies nicht zu tun, und Sie sind sicher, dass Sie wissen, was Sie tun.
_________________
1    Außer natürlich, dass die schickeren Formen der Parametererweiterung , zum Beispiel und , auf der Syntax aufbauen . Außerdem müssen Sie verwenden , etc., die 10., 11. zu referenzieren usw. Positionsparameter - Zitate werden nicht Ihnen dabei helfen.${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}

G-Man
quelle
2

Um Leerzeichen und Leerzeichen in Variablen zu behandeln, sollten Sie sie immer in doppelte Anführungszeichen setzen. Das Einstellen eines ordnungsgemäßen IFS ist ebenfalls eine gute Praxis.

Empfohlen: http://www.dwheeler.com/essays/filenames-in-shell.html

ramonovski
quelle