Unterschied zwischen einfachen und doppelten eckigen Klammern in Bash

161

Ich lese Bash-Beispiele über, ifaber einige Beispiele sind in eckigen Klammern geschrieben:

if [ -f $param ]
then
  #...
fi

andere mit doppelten eckigen Klammern:

if [[ $? -ne 0 ]]
then
    start looking for errors in yourlog
fi

Was ist der Unterschied?

rkmax
quelle
3
Sie können Ihre Antwort erhalten, indem Sie sich die Antwort auf diese Frage ansehen
Amir Naghizadeh

Antworten:

188

Einzelne []sind Posix Shell-konforme Zustandstests.

Double [[]]sind eine Erweiterung des Standards []und werden von Bash und anderen Shells (z. B. zsh, ksh) unterstützt. Sie unterstützen zusätzliche Operationen (sowie die Standard-Posix-Operationen). Zum Beispiel: ||anstelle von -ound Regex Matching mit =~. Eine ausführlichere Liste der Unterschiede finden Sie im Abschnitt Bash-Handbuch zu bedingten Konstrukten .

Verwenden []Sie diese Option, wenn Ihr Skript über mehrere Shells hinweg portierbar sein soll. Verwenden [[]]Sie diese Option, wenn Sie bedingte Ausdrücke möchten, die von nicht unterstützt werden []und nicht portierbar sein müssen.

cmh
quelle
6
Ich würde hinzufügen, dass Sie die portable Option verwenden sollten , wenn Ihr Skript nicht mit einem Shebang beginnt, der explizit eine Shell anfordert, die unterstützt [[ ]](z. B. Bash mit #!/bin/bashoder #!/usr/bin/env bash). Skripte, bei denen davon ausgegangen wird, dass / bin / sh solche Erweiterungen unterstützt, funktionieren unter Betriebssystemen wie den jüngsten Debian- und Ubuntu-Versionen, bei denen dies nicht der Fall ist.
Gordon Davisson
87

Verhaltensunterschiede

Getestet in Bash 4.3.11:

  • POSIX vs Bash Erweiterung:

  • reguläres Kommando gegen Magie

    • [ ist nur ein regulärer Befehl mit einem seltsamen Namen.

      ]ist nur ein Argument dafür [, dass keine weiteren Argumente verwendet werden.

      Ubuntu 16.04 hat tatsächlich eine ausführbare Datei, /usr/bin/[die von coreutils bereitgestellt wird, aber die in bash integrierte Version hat Vorrang.

      An der Art und Weise, wie Bash den Befehl analysiert, wird nichts geändert.

      Insbesondere <wird umgeleitet &&und ||mehrere Befehle verkettet, es werden ( )Unterschalen generiert, sofern sie nicht entkommen \, und die Worterweiterung erfolgt wie gewohnt.

    • [[ X ]]ist ein einzelnes Konstrukt, das Xmagisch analysiert werden kann. <, &&, ||Und ()sind speziell, und einzelne Wörter aufgespalten Regeln behandelt unterschiedlich.

      Es gibt auch weitere Unterschiede wie =und =~.

      In Bashese: [ist ein integrierter Befehl und [[ein Schlüsselwort: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && und ||

    • [[ a = a && b = b ]]: wahr, logisch und
    • [ a = a && b = b ]: Syntaxfehler, &&analysiert als UND-Befehlstrennzeichencmd1 && cmd2
    • [ a = a -a b = b ]: äquivalent, aber von POSIX³ veraltet
    • [ a = a ] && [ b = b ]: POSIX und zuverlässiges Äquivalent
  • (

    • [[ (a = a || a = b) && a = b ]]: falsch
    • [ ( a = a ) ]: Syntaxfehler, ()wird als Subshell interpretiert
    • [ \( a = a -o a = b \) -a a = b ]: äquivalent, wird jedoch ()von POSIX nicht mehr unterstützt
    • { [ a = a ] || [ a = b ]; } && [ a = b ]POSIX-Äquivalent 5
  • Wortaufteilung und Dateinamengenerierung bei Erweiterungen (split + glob)

    • x='a b'; [[ $x = 'a b' ]]: true, Anführungszeichen nicht erforderlich
    • x='a b'; [ $x = 'a b' ]: Syntaxfehler, erweitert auf [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: Syntaxfehler, wenn sich mehr als eine Datei im aktuellen Verzeichnis befindet.
    • x='a b'; [ "$x" = 'a b' ]: POSIX-Äquivalent
  • =

    • [[ ab = a? ]]: true, weil es Pattern Matching macht ( * ? [sind magisch). Wird nicht auf Dateien im aktuellen Verzeichnis erweitert.
    • [ ab = a? ]: a?glob erweitert sich. Dies kann je nach den Dateien im aktuellen Verzeichnis wahr oder falsch sein.
    • [ ab = a\? ]: false, keine Glob-Erweiterung
    • =und ==sind in beiden gleich [und [[, ist aber ==eine Bash-Erweiterung.
    • case ab in (a?) echo match; esac: POSIX-Äquivalent
    • [[ ab =~ 'ab?' ]]: false 4 , verliert Magie mit''
    • [[ ab? =~ 'ab?' ]]: wahr
  • =~

    • [[ ab =~ ab? ]]: true, POSIX- Übereinstimmung für erweiterte reguläre Ausdrücke , ?nicht global erweitert
    • [ a =~ a ]: Syntax-Fehler. Kein Bash-Äquivalent.
    • printf 'ab\n' | grep -Eq 'ab?': POSIX-Äquivalent (nur einzeilige Daten)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': POSIX-Äquivalent.

Empfehlung : immer verwenden [].

Für jedes [[ ]]Konstrukt, das ich gesehen habe, gibt es POSIX-Entsprechungen .

Wenn Sie [[ ]]Sie verwenden:

  • Portabilität verlieren
  • zwingen Sie den Leser, die Feinheiten einer anderen Bash-Erweiterung zu lernen. [ist nur ein regulärer Befehl mit einem seltsamen Namen, es handelt sich nicht um eine spezielle Semantik.

¹ Inspiriert von dem äquivalenten [[...]]Konstrukt in der Korn-Schale

² schlägt jedoch für einige Werte von aoder b(wie +oder index) fehl und führt einen numerischen Vergleich durch, wenn aund bwie Dezimalzahlen aussehen. expr "x$a" '<' "x$b"arbeitet um beide herum.

³ und schlägt auch für einige Werte von aoder bwie !oder fehl (.

4 in Bash 3.2 und höher und vorausgesetzt, die Kompatibilität mit Bash 3.1 ist nicht aktiviert (wie bei BASH_COMPAT=3.1)

5 obwohl die Gruppierung (hier mit der {...;}Befehlsgruppe, anstelle der (...)eine unnötige Subshell ausgeführt werden würde) nicht erforderlich ist, da die Operatoren ||und &&shell (im Gegensatz zu den Operatoren ||und && [[...]]oder den Operatoren -o/ -a [) die gleiche Priorität haben. Wäre [ a = a ] || [ a = b ] && [ a = b ]also gleichwertig.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
3
" Es gibt POSIX-Äquivalente für jedes [[]] Konstrukt, das ich gesehen habe. " Dasselbe gilt für jede Turing Complete- Sprache auf dem Planeten.
A. Rick
3
@ A.Rick, das wäre eine gültige Antwort auf alle SO-Fragen "Wie man X in Sprache Y macht" SO :-) Natürlich ist in dieser Aussage ein "bequemer" impliziter Punkt enthalten.
Ciro Santilli 法轮功 冠状 病 六四 事件 16
6
Fantastische Zusammenfassung. Danke für die Mühe. Ich bin jedoch mit der Empfehlung nicht einverstanden. Es ist Portabilität im Vergleich zu konsistenterer und leistungsfähigerer Syntax. Wenn Sie in Ihren Umgebungen bash> 4 benötigen können, wird die Syntax [[]] empfohlen.
Bernard
@ Downvoters bitte erklären, damit ich Informationen lernen und verbessern kann :-)
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功
8
Tolle Antwort, aber ich denke, die Empfehlung : Immer verwenden[] sollte als Meine Präferenz gelesen werden : Verwenden, []wenn Sie nicht die Portabilität verlieren möchten . Wie hier angegeben : Wenn Portabilität / Konformität mit POSIX oder der BourneShell ein Problem darstellt, sollte die alte Syntax verwendet werden. Wenn das Skript andererseits BASH, Zsh oder KornShell erfordert, ist die neue Syntax normalerweise flexibler, aber nicht unbedingt abwärtskompatibel. Ich würde lieber mitgehen, [[ ab =~ ab? ]]wenn ich kann und keine Bedenken hinsichtlich der Abwärtskompatibilität haben alsprintf 'ab' | grep -Eq 'ab?'
MauricioRobayo
14

In einzelnen Klammern für den Zustandstest (dh [...]) werden einige Operatoren wie single =von allen Shells unterstützt, während die Verwendung des Operators ==von einigen älteren Shells nicht unterstützt wird.

In doppelten Klammern für den Zustandstest (dh [...]) gibt es keinen Unterschied zwischen der Verwendung =oder ==in alten oder neuen Schalen.

Bearbeiten: Ich sollte auch Folgendes beachten: Verwenden Sie in Bash nach Möglichkeit immer doppelte Klammern [[...]], da dies sicherer ist als einfache Klammern. Ich werde anhand des folgenden Beispiels veranschaulichen, warum:

if [ $var == "hello" ]; then

Wenn $ var zufällig null / leer ist, sieht das Skript Folgendes:

if [ == "hello" ]; then

das wird dein Skript brechen. Die Lösung besteht darin, entweder doppelte Klammern zu verwenden oder immer daran zu denken, Ihre Variablen in Anführungszeichen zu setzen ( "$var"). Doppelte Klammern sind eine bessere defensive Codierungspraxis.

Sampson-Chen
quelle
1
Das Setzen von Anführungszeichen um alle Lesevorgänge von Variablen, es sei denn, Sie haben einen sehr guten Grund, dies nicht zu tun, ist eine viel bessere defensive Codierungspraxis, da dies für alle Lesevorgänge von Variablen gilt, nicht nur für diejenigen unter Bedingungen. Ein iTunes-Installationsfehler hat einmal die Dateien von Personen gelöscht, wenn der Name der Festplatte Leerzeichen (oder ähnliches) enthielt. Es löst auch das von Ihnen erwähnte Problem.
Chai T. Rex
1

Sie können die doppelten eckigen Klammern für die Anpassung der leichten Regex verwenden, z.

if [[ $1 =~ "foo.*bar" ]] ; then

(Solange die von Ihnen verwendete Bash-Version diese Syntax unterstützt)

asf107
quelle
6
Außer Sie haben das Muster zitiert, sodass es jetzt als Literalzeichenfolge behandelt wird.
Ormaaj
sehr richtig. manchmal nervt mich das :)
asf107
1

Bash Handbuch sagt:

Bei Verwendung mit [[sortieren die Operatoren '<' und '>' lexikografisch nach dem aktuellen Gebietsschema. Der Testbefehl verwendet die ASCII-Reihenfolge.

(Der Testbefehl ist identisch mit [])

user5500105
quelle