Ist test oder portabler sowohl zwischen bash shells als auch zwischen anderen shells?

44

Ich sehe, ich kann es tun

$ [ -w /home/durrantm ] && echo "writable"
writable

oder

$ test -w /home/durrantm && echo "writable"
writable

oder

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Ich verwende gerne die dritte Syntax. Sind sie in jeder Hinsicht und für alle Negativ- und Kantenfälle gleichwertig? Gibt es Unterschiede in der Portabilität, z. B. zwischen Bash unter Ubuntu und OS X oder älteren / neueren Bash-Versionen, z. B. vor / nach 4.0, und erweitern beide Ausdrücke auf dieselbe Weise?

Michael Durrant
quelle
2
Für [ … ]vs [[ … ]]vs test …gibt es vollständigere Antworten in dieser meist doppelten Frage .
Gilles 'SO - hör auf, böse zu sein'
Informationen zum Testen der Beschreibbarkeit einer Datei finden Sie unter So testen Sie nichtinvasiv den Schreibzugriff auf eine Datei
G-Man sagt, dass Monica
1
Es gibt ein Sprichwort: "Es gibt keinen tragbaren Code, nur Code, der portiert wurde". Mein Rat dazu: Verwenden Sie das am besten lesbare Formular (wahrscheinlich [...]) und probieren Sie es auf allen Plattformen aus, die Sie unterstützen möchten. Es ist nicht sinnvoll, Ihre Skripte zu verschleiern, sodass sie auf alten Plattformen ausgeführt werden, die weder Sie noch Ihre Zielgruppe verwenden. Es macht Ihren Code nur schwer zu lesen, führt zu unnötigen Fehlern und möglicherweise sogar zu Sicherheitsproblemen (wie es bei openssl der Fall war).
stefan.schwetschke 18.11.14

Antworten:

31

[ist ein Synonym für den testBefehl und gleichzeitig ein integrierter und ein separater Befehl. Ist [[aber ein Bash-Schlüsselwort und funktioniert nur in einigen Versionen. Aus Gründen der Portabilität ist es daher besser, single []oder zu verwendentest

[ -w "/home/durrantm" ] && echo "writable"
Costas
quelle
2
[ist POSIX eingebaut oder Bash eingebaut?
Sandburg
@Sandburg POSIX ist ein Standard, daher können keine integrierten Funktionen verwendet werden. Es definiert nicht, ob etwas in den Interpreter eingebaut werden soll oder nicht, nur was es zu tun hat. Diese Regeln gelten für beide Formen, testund [gleichermaßen. Wenn eine Shell die Auswertung selbstständig durchführen kann, erspart dies Ihnen einen Prozess und beschleunigt ihn etwas, aber das Ergebnis muss dasselbe sein.
Bachsau,
@Bachsau, die Sandburgs Frage, ob das Verhalten [von POSIX definiert und daher maximal portierbar ist oder nicht, immer noch nicht beantwortet (das scheint der springende Punkt dieser Frage zu sein, wenn ich mich nicht irre)
JamesTheAwesomeDude
@ Sandburg (und an alle anderen, die darüber stolpern): Ja, es sieht aus wie beides [und test ist POSIX . Es scheint weder "empfohlen" noch zu geben, obwohl der einzige Unterschied (?), Den ich zwischen ihnen feststellen kann, darin besteht, dass test"das Argument" - "[als Begrenzer für das Ende von Optionen] nicht erkannt wird" (? - dies impliziert "). - " wird als Argumentationsende anerkannt [, wofür ich mir sowieso keinen guten Testfall ausdenken kann . Aber ich schweife ab. Benutze was du willst. IMO, [sieht elegant aus, ist aber testeindeutig ein Befehl.)
JamesTheAwesomeDude
35

Ja, da gibt es Unterschiede. Die tragbarsten sind testoder [ ]. Diese sind beide Teil der POSIX- testSpezifikation .

Das if ... fiKonstrukt wird ebenfalls von POSIX definiert und sollte vollständig portierbar sein.

Das [[ ]]ist ein kshMerkmal, das auch in einigen Versionen von bash( allen modernen ), in zshund vielleicht in anderen, aber nicht in shoder dashoder den verschiedenen anderen einfacheren Schalen vorhanden ist.

Also, um Ihre Skripte tragbar, verwenden [ ], testoder if ... fi.

terdon
quelle
10
Nur zur Kenntnis zu nehmen, bashund zshseit [[sehr langer Zeit unterstützt ( bashfügte es in den späten 90ern hinzu, zshspätestens 2000, und ich wäre überrascht, wenn es jemals an Unterstützung mangelte), so dass es unwahrscheinlich ist, dass Sie auf eine Version von beidem ohne stoßen [[. Es dashist weitaus wahrscheinlicher, auf eine andere POSIX-kompatible Shell (z. B. ) zu stoßen.
Chepner
1
Interessant [[ist, dass Parametererweiterungen nicht in Anführungszeichen gesetzt werden müssen: [[-f $file]]event funktioniert, wenn $fileLeerzeichen enthalten sind.
Hilfemethode
2
@Helpermethode auch in regulären Ausdrücken gebaut[[ $a =~ ^reg.*exp.*$' ]]
GnP
20

Bitte beachten Sie, dass dies [] && cmdnicht mit der if .. fiKonstruktion identisch ist .

Manchmal ist sein Verhalten ziemlich ähnlich und Sie können [] && cmdstattdessen verwenden if .. fi. Aber nur manchmal. Wenn Sie mehr als einen Befehl zur Ausführung haben, müssen Sie if .. else .. fivorsichtig sein und die Logik überprüfen.

Einige Beispiele:

[ -z "$VAR" ] && ls file || echo wiiii

Ist nicht dasselbe wie

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

Denn wenn dies lsfehlschlägt, echowird ausgeführt, was mit nicht passieren wird if.

Ein anderes Beispiel:

[ -z "$VAR" ] && ls file && echo wiii

ist nicht dasselbe wie

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

obwohl diese Konstruktion gleich wirkt

[ -z "$VAR" ] && { ls file ; echo wiii ; }

Bitte beachten Sie, ;nachdem Echo wichtig ist und da sein muss.

Die obige wiederaufgenommene Aussage können wir also sagen

[] && cmd == Wenn der erste Befehl erfolgreich ist, führen Sie den nächsten aus

if .. fi == Wenn Bedingung (die auch der Testbefehl sein kann), dann Befehl (e) ausführen

Also für die Portabilität zwischen [und [[verwenden [nur.

ifist POSIX-kompatibel. Also , wenn Sie wählen zwischen [und ifwählen bei Ihrer Aufgabe und erwartetes Verhalten suchen.

eilen
quelle
Ich bin wahrscheinlich nur dicht, aber ich sehe nicht, was der Unterschied sein würde. Könnten Sie bitte ein Beispiel nennen, bei dem diese beiden Konstruktionen unterschiedliche Ergebnisse liefern würden?
Übelsuppe
1
@evilsoup, aktualisiert. Vielleicht bin ich nicht der beste Erklärer, obwohl ich hoffe, dass es jetzt klar ist.
Ansturm
1
Sehr gute Punkte, Ansturm. Ich nehme eine sichere Form Ihres ersten Beispiel wäre: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. Es ist etwas ausführlicher, aber immer noch kürzer als die if...fiKonstruktion.
PM 2Ring
Ich habe die Frage nach [vs [[vs test gestellt und nicht nach ... fi vs &&, damit es EINE Frage ist. Leider scheint diese Antwort dadurch falsch zu sein. Entschuldigung, dass Sie die Frage zunächst nicht richtig gestellt haben, die dazu geführt hat. Lebe und (versuche) zu lernen. :)
Michael Durrant
Ich habe das abgelehnt, weil a) dies hauptsächlich eine gute Antwort ist, aber es ist eine Antwort auf eine andere Frage. b) Sie präsentieren [und ifals Stellvertreter, aber sie sind nicht. Es ist eigentlich das &&, was das ersetzt if. [Führt einen Code aus und gibt einen Status zurück, ähnlich wie lsund grepwürde. ifVerzweigt die Ausführung in Abhängigkeit vom Rückgabestatus des Befehls (Anweisung), der nach dem if angegeben wird. Dies kann ein beliebiger Befehl (Anweisung) sein. &&Führt die nächste Anweisung nur aus, wenn die vorherige, ähnlich wie eine einfache, 0 zurückgibt if..then..fi.
GnP
9

Es ist eigentlich das &&, was das ersetzt if, nicht das test: Eine ifAnweisung in Shell-Skripten prüft, ob ein Befehl einen "erfolgreichen" (Null) Exit-Status zurückgegeben hat. In Ihrem Beispiel lautet der Befehl [.

Es gibt also tatsächlich zwei Dinge, die Sie hier variieren: den Befehl zum Ausführen des Tests und die Syntax zum Ausführen von Code basierend auf dem Ergebnis dieses Tests.

Testbefehle:

  • testist ein standardisierter Befehl zum Auswerten der Eigenschaften von Zeichenfolgen und Dateien. In Ihrem Beispiel führen Sie den Befehl austest -w /home/durrantm
  • [ist ein Alias ​​dieses Befehls, der gleichermaßen standardisiert ist und dessen letztes Argument obligatorisch ist ], um wie ein Ausdruck in eckigen Klammern auszusehen. Lass dich nicht täuschen, es ist immer noch nur ein Befehl (vielleicht findest du sogar, dass dein System eine Datei mit dem Namen hat /bin/[)
  • [[ist eine erweiterte Version des Testbefehls, der in einige Shells integriert ist, jedoch nicht Teil desselben POSIX-Standards ist. Es enthält zusätzliche Optionen, die Sie hier nicht verwenden

Bedingte Ausdrücke:

  • Der &&Operator ( hier standardisiert ) führt eine logische UND-Operation durch, indem er zwei Befehle auswertet und 0 zurückgibt (was true darstellt), wenn beide 0 zurückgeben; Der zweite Befehl wird nur ausgewertet, wenn der erste Null zurückgibt, sodass er als einfache Bedingung verwendet werden kann
  • Das if ... then ... fiKonstrukt ( hier standardisiert ) verwendet die gleiche Methode zur Beurteilung der "Wahrheit", erlaubt jedoch eine zusammengesetzte Liste von Aussagen in der thenKlausel anstelle des einzelnen Befehls, den ein &&Kurzschluss bietet, und liefert elifund elseKlauseln, die schwer zu schreiben sind mit nur &&und ||. Beachten Sie, dass die Bedingung in einer ifAnweisung nicht in eckige Klammern eingeschlossen ist .

Die folgenden Darstellungen sind also alle gleichermaßen portabel und vollständig äquivalent zu Ihrem Beispiel:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Zwar sind auch die folgenden gleichwertig, aber aufgrund der Nicht-Standard-Natur weniger portabel [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi
IMSoP
quelle
Ja, ich habe den Teil if .... fi entfernt, damit dies 1 Frage ist.
Michael Durrant
7

Verwenden Sie aus Gründen der Portabilität test/ [. Aber wenn Sie die Portabilität nicht benötigen, verwenden Sie sie aus Gründen der Gesundheit von Ihnen und anderen, die Ihr Skript lesen [[. :)

Siehe auch What is the difference between test, [ and [[ ?in der BashFAQ .

PM 2Ring
quelle
7

Wenn Sie Portabilität außerhalb der Bourne-ähnlichen Welt wünschen, dann:

test -w /home/durrantm && echo writable

ist das tragbarste. Es funktioniert in Schalen der Bourne cshund rcFamilien.

test -w /home/durrantm && echo "writable"

ausgegeben würden , "writable"anstatt writablein Schalen der rcFamilie ( rc, es, akanga, wo "ist nichts Besonderes).

[ -w /home/durrantm ] && echo writable

würde nicht in Shells von cshoder rcFamilien auf Systemen funktionieren, in denen kein [Befehl vorhanden ist $PATH(von einigen ist bekannt, dass testsie einen [Alias haben, aber nicht ).

if [ -w /home/durrantm ]; then echo writabe; fi

Funktioniert nur in Schalen der Bourne-Familie.

[[ -w /home/durrantm ]] && echo writable

Funktioniert nur in ksh(woher es stammt) zshund bash(alle 3 in der Bourne-Familie).

Keiner würde in der fishShell arbeiten, wo Sie brauchen:

[ -w /home/durrantm ]; and echo writable

oder:

if [ -w /home/durrantm ]; echo writable; end
Stéphane Chazelas
quelle
0

Mein wichtigster Grund für die Auswahl von entweder if foo; then bar; fioder foo && barist, ob der Beendigungsstatus des gesamten Befehls wichtig ist.

vergleichen Sie:

#!/bin/sh
set -e
foo && bar
do_baz

mit:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Sie könnten denken, dass sie dasselbe tun; Wenn foodies jedoch fehlschlägt (oder falsch ist, abhängig von Ihrer Sichtweise), wird do_baz im ersten Beispiel nicht ausgeführt, da das Skript beendet wurde ... Die set -eShell wird angewiesen, sofort zu beenden, wenn ein Befehl einen falschen Status zurückgibt . Sehr nützlich, wenn Sie Dinge tun wie:

cd /some/directory
rm -rf *

Sie möchten nicht, dass das Skript weiter ausgeführt wird, wenn das Skript cdaus irgendeinem Grund fehlschlägt.

wurtel
quelle
1
No a failing foobricht das Skript in beiden Fällen nicht ab. Dies ist ein Sonderfall für set -e(wenn der Befehl als Bedingung ausgewertet wird (links von einigen && / || oder in if / while / until / elsif ... -Zuständen). In barbeiden Fällen würde ein Fehler die Shell verlassen.
Stéphane Chazelas
2
Sie möchten cd /some/directory && rm -rf -- *oder cd /some/directory || exit; rm -rf -- *(entfernt versteckte Dateien immer noch nicht). Ich persönlich mag es nicht, wenn ich mich nicht set -ebemühe, richtigen Code zu schreiben.
Stéphane Chazelas