Sind doppelte eckige Klammern [[]] gegenüber einfachen eckigen Klammern [] in Bash vorzuziehen?

574

Ein Mitarbeiter behauptete kürzlich in einer Codeüberprüfung, dass das [[ ]]Konstrukt [ ]in Konstrukten wie vorzuziehen sei

if [ "`id -nu`" = "$someuser" ] ; then 
     echo "I love you madly, $someuser"
fi

Er konnte keine Begründung liefern. Ist dort eines?

Leonard
quelle
19
Seien Sie flexibel, erlauben Sie sich manchmal, einen Rat anzuhören, ohne seine ausführliche Erklärung zu benötigen :) [[Der Code ist gut und klar, aber denken Sie an den Tag, an dem Sie Ihre Scriptworks auf das System mit der Standard-Shell portieren, die nicht bashoder kshist. etc. [ist hässlicher, umständlich, funktioniert aber wie AK-47in jeder Situation.
Turm
178
@rook Sie können sich einen Rat ohne eine tiefe Erklärung anhören, klar. Aber wenn Sie eine Erklärung anfordern und diese nicht erhalten, ist dies normalerweise eine rote Fahne. "Vertrauen, aber überprüfen" und so weiter.
Josip Rodin
1
@rook mit anderen Worten "Tu, was dir gesagt wurde und stelle keine Fragen"
Glyphe
@rook Das ergab keinen Sinn.
Rakib

Antworten:

607

[[hat weniger Überraschungen und ist im Allgemeinen sicherer zu bedienen. Aber es ist nicht portabel - POSIX gibt nicht an, was es tut, und nur einige Shells unterstützen es (neben Bash habe ich gehört, dass ksh es auch unterstützt). Zum Beispiel können Sie tun

[[ -e $b ]]

um zu testen, ob eine Datei vorhanden ist. Aber mit [muss man zitieren $b, weil es das Argument spaltet und Dinge wie erweitert "a*"(wo [[es wörtlich genommen wird). Das hat auch damit zu tun, wie [ein externes Programm sein kann und wie jedes andere Programm seine Argumentation erhält (obwohl es auch ein eingebautes sein kann, aber dann hat es immer noch nicht diese spezielle Behandlung).

[[hat auch einige andere nette Funktionen, wie das Abgleichen von regulären Ausdrücken mit =~zusammen mit Operatoren, wie sie in C-ähnlichen Sprachen bekannt sind. Hier ist eine gute Seite darüber: Was ist der Unterschied zwischen Test [und [[? und Bash-Tests

Johannes Schaub - litb
quelle
21
In Anbetracht der Tatsache, dass Bash heutzutage überall ist, denke ich, dass es verdammt tragbar ist. Die einzige häufige Ausnahme für mich sind Busybox-Plattformen - aber angesichts der Hardware, auf der es ausgeführt wird, möchten Sie wahrscheinlich trotzdem besondere Anstrengungen unternehmen.
Waffen
14
@ Waffen: In der Tat. Ich würde vorschlagen, dass Ihr zweiter Satz Ihren ersten widerlegt; Wenn Sie die Softwareentwicklung als Ganzes betrachten, sind "Bash ist überall" und "Die Ausnahme sind Busybox-Plattformen" völlig inkompatibel. Busybox ist für die Embedded-Entwicklung weit verbreitet.
Leichtigkeitsrennen im Orbit
11
@guns: Selbst wenn bash auf meiner Box installiert ist, wird das Skript möglicherweise nicht ausgeführt (möglicherweise habe ich / bin / sh mit einer Dash-Variante verknüpft, wie es bei FreeBSD und Ubuntu Standard ist). Es gibt auch eine zusätzliche Verrücktheit bei Busybox: Mit Standard-Kompilierungsoptionen wird es heutzutage analysiert, [[ ]]aber so interpretiert, dass es dasselbe bedeutet wie [ ].
Dubiousjim
59
@dubiousjim: Wenn Sie nur Bash-Konstrukte verwenden (wie dieses), sollten Sie #! / bin / bash haben, nicht #! / bin / sh.
Nick Matteo
16
Aus diesem Grund verwende ich meine Skripte #!/bin/sh, #!/bin/bashwechsle sie dann jedoch zur Verwendung , sobald ich mich auf eine BASH-spezifische Funktion verlasse, um anzuzeigen, dass sie nicht mehr über die Bourne-Shell portierbar ist.
Anthony
151

Verhaltensunterschiede

Einige Unterschiede zu 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 dafür zur /usr/bin/[Verfügung gestellt von coreutils , 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, ( )Subshells generiert, sofern dies nicht durchbrochen wird \, 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 ) ]: Syntax-Fehler, () wird als Subshell interpretiert
    • [ \( a = a -o a = b \) -a a = b ]: äquivalent, aber () von POSIX nicht mehr unterstützt
    • { [ a = a ] || [ a = b ]; } && [ a = b ] POSIX-Äquivalent⁵
  • Wortteilung 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. 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⁴, 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 (.

⁴ 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)

⁵ obwohl die Gruppierung (hier mit der {...;}Befehlsgruppe, anstelle der (...)eine unnötige Subshell ausgeführt wird) 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
4
Gute Erklärungen. Ich benutze [aus den gleichen Gründen.
Gordon
2
In Bashese :) ha. Schöne Klarstellung der Unterschiede und Gründe, bei POSIX zu bleiben.
Jonathan Komar
56

[[ ]]bietet weitere Funktionen - Ich schlage vor, dass Sie sich das Advanced Bash Scripting Guide ansehen, um weitere Informationen zu erhalten, insbesondere den Abschnitt mit den erweiterten Testbefehlen in Kapitel 7. Tests .

Übrigens wurde, wie der Leitfaden feststellt, [[ ]]in ksh88 (der 1988er Version der Korn-Shell) eingeführt.

Nils von Barth
quelle
5
Dies ist alles andere als ein Bashismus, sondern wurde erstmals in der Korn-Shell eingeführt.
Henk Langeveld
6
@Thomas, das ABS wird in vielen Kreisen als sehr schlechte Referenz angesehen; Obwohl es viele genaue Informationen enthält, achtet es sehr wenig darauf, in seinen Beispielen keine schlechten Praktiken zu zeigen, und verbringt einen Großteil seines Lebens ohne Wartung.
Charles Duffy
@ CharlesDuffy danke für deinen Kommentar, kannst du eine gute Alternative nennen? Ich bin kein Experte für Shell-Skripte. Ich suche nach einem Leitfaden, den ich etwa alle halbe Jahre zum Schreiben eines Skripts konsultieren kann.
Thomas
9
@Thomas, das Wooledge BashFAQ und das dazugehörige Wiki sind das, was ich benutze; Sie werden aktiv von den Bewohnern des Freenode # bash-Kanals gepflegt (die, obwohl sie manchmal stachelig sind, sich sehr um die Korrektheit kümmern). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 , ist die Seite, die für diese Frage direkt relevant ist.
Charles Duffy
13

Von welchem ​​Komparator, Test, Klammer oder Doppelklammer ist der schnellste? ( http://bashcurescancer.com )

Die doppelte Klammer ist ein „zusammengesetzter Befehl“, bei dem test und die einfache Klammer Shell-integrierte Funktionen sind (und in Wirklichkeit der gleiche Befehl sind). Somit führen die einfache Klammer und die doppelte Klammer unterschiedlichen Code aus.

Der Test und die einzelne Klammer sind am portabelsten, da sie als separate und externe Befehle existieren. Wenn Sie jedoch eine moderne Version von BASH verwenden, wird die doppelte Halterung unterstützt.

f3lix
quelle
7
Was ist mit der Besessenheit, in Shell-Skripten am schnellsten zu sein? Ich möchte es am tragbarsten und könnte mich nicht weniger darum kümmern, welche Verbesserung [[es bringen könnte. Aber dann bin ich ein alter Furz der alten Schule :-)
Jens
1
Ich denke, viele Shells beweisen eingebaute Versionen von [und testobwohl es auch externe Versionen gibt.
Dubiousjim
4
@Jens im Allgemeinen stimme ich zu: Der gesamte Zweck von Skripten ist (war?) Portabilität (andernfalls würden wir Code & Kompilieren, nicht Skripte) ... die beiden Ausnahmen, die mir einfallen, sind: (1) Tab-Vervollständigung (wo Abschlussskripte können mit viel bedingter Logik sehr lang werden); und (2) Super-Eingabeaufforderungen ( PS1=...crazy stuff...und / oder $PROMPT_COMMAND); Für diese möchte ich keine spürbare Verzögerung bei der Ausführung des Skripts.
Michael
1
Ein Teil der Besessenheit mit dem schnellsten ist einfach Stil. Wenn alles andere gleich ist, können Sie das effizientere Codekonstrukt in Ihren Standardstil integrieren, insbesondere wenn dieses Konstrukt auch eine bessere Lesbarkeit bietet. In Bezug auf die Portabilität sind viele Aufgaben, für die Bash geeignet ist, von Natur aus nicht portierbar. ZB muss ich laufen, apt-get updatewenn es mehr als X Stunden her ist, seit es das letzte Mal ausgeführt wurde. Es ist eine große Erleichterung, wenn man die Portabilität von der bereits zu langen Liste von Einschränkungen für Code weglassen kann.
Ron Burk
5

Wenn Sie den Styleguide von Google befolgen möchten :

Test [und[[

[[ ... ]]Reduziert Fehler, da zwischen [[und keine Pfadnamenerweiterung oder Wortaufteilung stattfindet ]], und [[ ... ]]ermöglicht den Abgleich regulärer Ausdrücke, wenn [ ... ]dies nicht der Fall ist.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi
crizCraig
quelle
1
Google schrieb " keine Pfadnamenerweiterung ... findet statt ", [[ -d ~ ]]gibt jedoch true zurück (was impliziert, dass ~auf erweitert wurde /home/user). Ich denke, Google hätte präziser schreiben sollen.
JamesThomasMoon1979
2

Eine typische Situation, in der Sie nicht verwenden können, [[ist in einem Autotools-Skript configure.ac. Dort haben Klammern eine spezielle und andere Bedeutung, sodass Sie testanstelle von [oder [[- Beachten Sie, dass test und [dasselbe Programm sind.

Vicente Bolea
quelle
2
Angesichts der Tatsache, dass Autotools keine POSIX-Shell sind, warum sollten Sie jemals erwarten [, als POSIX-Shell-Funktion definiert zu werden?
Gordon
Weil das Autoconf-Skript wie ein Shell-Skript aussieht und ein Shell-Skript erzeugt und die meisten Shell-Befehle darin ausgeführt werden.
vy32
-1

[[]] doppelte Klammern werden unter bestimmten Versionen von SunOS nicht unterstützt und innerhalb von Funktionsdeklarationen vollständig nicht unterstützt von: GNU bash, Version 2.02.0 (1) -release (sparc-sun-solaris2.6)

Assgeier
quelle
1
sehr wahr und überhaupt nicht belanglos. Die Bash-Portabilität über ältere Versionen hinweg muss berücksichtigt werden. Die Leute sagen, "Bash ist allgegenwärtig und portabel, außer vielleicht (hier ein esoterisches Betriebssystem einfügen) " - aber meiner Erfahrung nach ist Solaris eine jener Plattformen, auf denen der Portabilität besondere Aufmerksamkeit geschenkt werden muss: nicht nur, um ältere Bash-Versionen auf einem zu berücksichtigen neueres Betriebssystem, Probleme / Fehler mit Arrays, Funktionen usw.; Aber auch Dienstprogramme (in den Skripten verwendet) wie tr, sed, awk, tar haben Kuriositäten und Besonderheiten bei Solaris, die Sie umgehen müssen.
Michael
2
Sie haben so Recht ... so viele Dienstprogramme, die nicht POSIX unter Solaris sind, sehen Sie sich nur die Ausgabe und die Argumente "df" an ... Schade unter Sun. Hoffentlich verschwindet es nach und nach (außer in Kanada).
Aasfresser
7
Solaris 2.6 im Ernst? Es wurde 1997 veröffentlicht und beendete den Support 2006. Ich denke, wenn Sie das noch verwenden, haben Sie andere Probleme!. Übrigens wurde Bash v2.02 verwendet, das doppelte Klammern einführte, sodass es auch bei so etwas Altem funktionieren sollte. Solaris 10 aus dem Jahr 2005 verwendete Bash 3.2.51 und Solaris 11 aus dem Jahr 2011 verwendete Bash 4.1.11.
Peterh
1
Re Script Portabilität. Auf Linux-Systemen gibt es im Allgemeinen nur eine Edition jedes Tools, und das ist die GNU-Edition. Unter Solaris haben Sie normalerweise die Wahl zwischen einer nativen Solaris-Edition oder der GNU-Edition (z. B. Solaris-Tar oder GNU-Tar). Wenn Sie von GNU-spezifischen Erweiterungen abhängig sind, müssen Sie dies in Ihrem Skript angeben, damit es portabel ist. Unter Solaris tun Sie dies, indem Sie "g" voranstellen, z. B. "ggrep", wenn Sie das GNU-Grep möchten.
Peterh
-2

Kurz gesagt, [[ist besser, weil es keinen anderen Prozess verzweigt. Keine Klammern oder eine einzelne Klammer ist langsamer als eine doppelte Klammer, da sie einen anderen Prozess verzweigt.

unix4linux
quelle
8
Test und [sind Namen für denselben eingebauten Befehl in bash. Versuchen Sie es mit type [, um dies zu sehen.
AB
1
@alberge, das stimmt, aber [[im Unterschied zu [, ist von der Syntax bash Befehlszeilen - Interpreter interpretiert. Versuchen Sie in Bash zu tippen type [[. unix4linux ist richtig, dass klassische Bourne-Shell- [Tests zwar einen neuen Prozess zur Bestimmung des Wahrheitswerts auslösen , die [[Syntax (von ksh durch bash, zsh usw. entlehnt) dies jedoch nicht tut.
Tim Gilbert
2
@Tim, ich bin mir nicht sicher, von welcher Bourne-Shell Sie sprechen, aber sie [ist sowohl in Bash als auch in Dash (der /bin/shin allen von Debian abgeleiteten Linux-Distributionen) integriert.
AB
1
Oh, ich verstehe was du meinst, das stimmt. Ich dachte an etwas wie beispielsweise / bin / sh auf älteren Solaris- oder HP / UX-Systemen, aber wenn Sie mit denen kompatibel sein müssten, würden Sie natürlich auch nicht verwenden.
Tim Gilbert
1
@alberge Bourne Shell ist nicht Bash (auch bekannt als Bourne Again SHell ).
Kiamlaluno