Fehler beim Shell Bracket-Test, wenn die Zeichenfolge in einer linken Klammer steht

27

Früher war ich mir sicher, dass das Zitieren von Zeichenfolgen immer eine gute Methode ist, um zu vermeiden, dass die Shell sie analysiert.

Dann bin ich auf folgendes gestoßen:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

Beim Versuch, das Problem einzugrenzen, wird derselbe Fehler angezeigt:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

Ich habe das Problem folgendermaßen gelöst:

[ "$x" = '1' ] && [ "$y" = '1' ]

Trotzdem muss ich wissen, was hier los ist.

Claudio
quelle
2
Als Workaround können Sie in bash[[ "$x" = '1' && "$y" = '1' ]]
terdon
3
Die POSIX-Testspezifikation beschreibt dies explizit -aund ist -oaus diesem Grund veraltet (was der [OB]hochgestellte Text neben der Spezifikation bedeutet). Wenn Sie [ "$x" = 1 ] && [ "$y" = 1 ]stattdessen schreiben, geht es Ihnen gut und Sie befinden sich im Bereich eines klar definierten / standardisierten Verhaltens.
Charles Duffy
6
Dies ist der Grund, warum die Leute verwendet haben, um [ "x$x" = "x1" ]zu verhindern, dass Argumente als Operatoren falsch interpretiert werden.
Jonathan Leffler
@ JonathanLeffler: Hey, du hast meine Antwort auf einen einzigen Satz zusammengefasst, nicht fair! :) Wenn man eine POSIX-Shell dashanstelle von Bash verwendet, ist dies immer noch eine nützliche Übung.
Nominal Animal
Ich möchte mich bei allen bedanken, die sich die Zeit genommen haben, meine Frage zu beantworten, sehr geschätzte Jungs :)! Ich möchte mich auch für die wichtige Bearbeitung meiner Frage bedanken. Wenn ich dieses Forum betrete, ist es manchmal genauso aufregend für mich, Alcatraz zu entkommen. Ein falscher Schritt bedeutet Ihr Leben. Auf jeden Fall wünsche ich mir, dass mein Dank Sie erreicht, bevor dieser Kommentar gelöscht wird
Claudio

Antworten:

25

Dies ist ein sehr undurchsichtiger Eckfall, bei dem man einen Fehler in der [Definition des integrierten Tests in Betracht ziehen könnte . Es entspricht jedoch dem Verhalten der tatsächlichen [Binärdatei, die auf vielen Systemen verfügbar ist. Soweit ich sagen kann, es wirkt sich nur auf bestimmte Fälle und eine Variable einen Wert hat, eine übereinstimmt [Operator wie (, !, =, -e, und so weiter.

Lassen Sie mich erklären, warum und wie man es in Bash- und POSIX-Shells umgeht.


Erläuterung:

Folgendes berücksichtigen:

x="("
[ "$x" = "(" ] && echo yes || echo no

Kein Problem; das obige ergibt keinen Fehler und gibt aus yes. So erwarten wir, dass Dinge funktionieren. Sie können die Vergleichszeichenfolge '1'wie gewünscht und den Wert von in ändern x, und es funktioniert wie erwartet.

Beachten Sie, dass sich die eigentliche /usr/bin/[Binärdatei genauso verhält. Wenn Sie zB laufen'/usr/bin/[' '(' = '(' ']' liegt kein Fehler vor, da das Programm erkennen kann, dass die Argumente aus einer einzelnen Zeichenfolgenvergleichsoperation bestehen.

Der Fehler tritt auf, wenn wir und mit einem zweiten Ausdruck. Es spielt keine Rolle, was der zweite Ausdruck ist, solange er gültig ist. Beispielsweise,

[ '1' = '1' ] && echo yes || echo no

gibt aus yesund ist offensichtlich ein gültiger Ausdruck; aber wenn wir die beiden kombinieren,

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

Bash lehnt den Ausdruck , wenn und nur wenn xist (oder! .

Wenn wir das oben genannte unter Verwendung des tatsächlichen [Programms ausführen würden , d.h.

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

Der Fehler wäre verständlich: Da die Shell die Variablensubstitutionen ausführt, die /usr/bin/[Binärdatei nur Parameter empfängt ( = ( -a 1 = 1und beendet ], kann sie verständlicherweise nicht analysieren, ob die offenen Klammern einen Unterausdruck beginnen oder nicht, da ein und vorhanden ist -Operation beteiligt ist. Sicher, das Parsen als zwei Zeichenfolgenvergleiche ist möglich, aber wenn Sie dies gierig tun, kann dies zu Problemen führen, wenn es auf korrekte Ausdrücke mit in Klammern gesetzten Unterausdrücken angewendet wird.

Das eigentliche Problem ist, dass sich die [eingebaute Shell genauso verhält, als würde sie den Wert von xvor der Prüfung des Ausdrucks erweitern.

(Diese und andere Unklarheiten im Zusammenhang mit der Variablenerweiterung waren ein wichtiger Grund für die Implementierung von Bash und empfehlen nun, stattdessen die [[ ... ]]Testausdrücke zu verwenden.)


Die Problemumgehung ist trivial und wird häufig in Skripten mit älteren shShells verwendet. Sie fügen häufig ein "sicheres" Zeichen xvor die Zeichenfolgen ein (beide Werte werden verglichen), um sicherzustellen, dass der Ausdruck als Zeichenfolgenvergleich erkannt wird:

[ "x$x" = "x(" -a "x$y" = "x1" ]
Nominelles Tier
quelle
1
Ich würde das Verhalten des [eingebauten nicht als Fehler bezeichnen. Wenn überhaupt, ist es ein inhärenter Konstruktionsfehler. [[ist ein Shell- Schlüsselwort , nicht nur ein eingebauter Befehl, mit dem Sie vor dem Entfernen von Anführungszeichen einen Blick auf die Dinge werfen und die übliche Wortteilung überschreiben können. zB [[ $x == 1 ]]muss nicht verwendet werden "$x", da sich ein [[Kontext von normal unterscheidet. Auf diese Weise [[können die Fallstricke vermieden werden [. POSIX erfordert [, wie es funktioniert verhalten, und bash ist meist POSIX - kompatibel , auch ohne --posix, so ändert [in ein Schlüsselwort unattraktiv ist.
Peter Cordes
Ihre Problemumgehung wird von POSIX nicht empfohlen. Verwenden Sie einfach zwei Anrufe an [.
Wildcard
@ PeterCordes: Ganz richtig; guter Punkt. (Ich hätte vielleicht designed verwenden sollen, anstatt es in meinem ersten Absatz zu definieren, aber da [das Verhalten durch die Historie vor POSIX belastet ist, habe ich stattdessen das letztere Wort gewählt.) Ich persönlich vermeide die Verwendung [[in meinen Beispielskripten, aber nur, weil es ein Ausnahme von der Zitierempfehlung, über die ich immer harpere (da das Weglassen von Zitaten der häufigste Grund für Skriptfehler ist), und ich habe mir keinen einfachen Absatz ausgedacht, um zu erklären, warum [[eine Ausnahme von den Regeln vorliegt, ohne meine Zitierempfehlung abzugeben vermuten.
Nominal Animal
@Wildcard: Nein. POSIX rät von dieser Vorgehensweise ab und darauf kommt es an. Nur weil eine Behörde diese Praxis nicht zufällig empfiehlt, macht das nicht schlimm. Wie ich bereits erwähne, handelt es sich hierbei um eine in shSkripten verwendete historische Praxis , die der POSIX-Standardisierung weit voraus ist. Mit geklammerten Unterausdrücken und -aund -ologische Operatoren in Tests / [ist effizienter , dass auf der Expression Chaining unter Berufung (über &&und ||); Es ist nur so, dass bei aktuellen Maschinen der Unterschied unerheblich ist.
Nominal Animal
@Wildcard: Ich persönlich bevorzuge es jedoch, die Testausdrücke mit &&und zu verketten ||, aber der Grund dafür ist, dass es für uns Menschen einfacher ist, sie zu pflegen (zu lesen, zu verstehen und zu ändern, falls erforderlich), ohne Fehler einzuführen. Ich kritisiere also nicht Ihren Vorschlag, sondern nur die Gründe für den Vorschlag. (Bei sehr ähnlichen Gründen die .LT., .GE.etc. Vergleichsoperator in Fortran 77 bekamen viel mehr menschenfreundliche Versionen <, >=usw. in späteren Versionen.)
Nenntier
11

[aka testsieht:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

testakzeptiert Unterausdrücke in Klammern; Daher wird angenommen, dass die linke Klammer einen Unterausdruck öffnet und versucht, ihn zu analysieren. Der Parser sieht das =als erstes in dem Unterausdruck und denkt, dass es sich um einen impliziten Stringlängentest handelt, also ist er glücklich. Auf den Unterausdruck sollte dann eine rechte Klammer folgen, und stattdessen findet der Parser 1statt ). Und es beschwert sich.

Wenn testgenau drei Argumente vorhanden sind und das mittlere Argument einer der erkannten Operatoren ist, wendet es diesen Operator auf das erste und dritte Argument an, ohne nach Unterausdrücken in Klammern zu suchen.

Für die vollständigen Details schauen Sie auf man bash, suchen Sie nach test expr.

Fazit: Der von verwendete Parsing-Algorithmus testist kompliziert. Verwenden Sie nur einfache Ausdrücke und verwenden Sie die Shell - Operatoren !, &&und ||sie zu kombinieren.

AlexP
quelle