Warum wird `|` nicht wörtlich in einem Glob-Muster behandelt?

13

Meine Frage kommt von: Wie vermeidet das Speichern des regulären Ausdrucks in einer Shell-Variablen Probleme beim Zitieren von Sonderzeichen für die Shell? .

  1. Warum liegt ein Fehler vor:

    $ [[ $a = a|b ]]  
    bash: syntax error in conditional expression: unexpected token `|'
    bash: syntax error near `|b'

    Innerhalb [[ ... ]]des zweiten Operanden von =wird erwartet, dass es sich um ein globales Muster handelt.

    Ist a|bkein gültiges Globbing-Muster? Können Sie angeben, gegen welche Syntaxregel verstoßen wird?

  2. In einigen Kommentaren wird darauf hingewiesen, dass dies |als Pipe interpretiert wird.

    Ändern Sie dann das =Glob-Muster in =~Regex-Muster, um die |Arbeit zu beginnen

    $ [[ $a =~ a|b ]]

    Ich habe in meinem vorherigen Beitrag von Learning Bash p180 gelernt, dass zu Beginn der Interpretation als Pipe erkannt wurde, noch bevor andere Interpretationsschritte durchgeführt wurden (einschließlich der Analyse der bedingten Ausdrücke in den Beispielen). Also, wie kann als Regex-Operator bei der Verwendung erkannt werden||=~ , ohne bei ungültiger Verwendung als Pipe erkannt zu werden, genau wie bei der Verwendung =? Das lässt mich denken, dass der Syntaxfehler in Teil 1 nicht bedeutet, dass er |als Pipe interpretiert wird.

    Jede Zeile, die die Shell aus der Standardeingabe oder einem Skript liest, wird als Pipeline bezeichnet. Es enthält einen oder mehrere Befehle, die durch null oder mehrere Pipe-Zeichen (|) getrennt sind. Für jede gelesene Pipeline teilt die Shell sie in Befehle auf, richtet die E / A für die Pipeline ein und führt dann für jeden Befehl die folgenden Schritte aus (Abbildung 7-1):

Vielen Dank.

StackExchange für alle
quelle
1
Beachten Sie, dass in einigen Versionen von bash das Parsing von Extglobs (wo etwas |Besonderes ist) standardmäßig rechts von aktiviert ist[[ $var = $pattern ]] . Es wäre interessant, die Versionen und shoptOptionskonfigurationen zu isolieren, bei denen dieses Verhalten auftritt. Wenn nur diejenigen extglobaktiviert sind, die entweder standardmäßig oder explizit konfiguriert sind, sind wir hier.
Charles Duffy
2
Übrigens, wenn Sie den Fall, dass das Pipe-Zeichen eine vorherige Stufe des Parsings stört, etwas umfassender ausschließen möchten (was meiner Meinung nach nicht der Fall ist, aber für den Leser nicht so offensichtlich ist, wie es sein könnte), würden Sie verwenden Sie pattern='a|b'und erweitern Sie dann $patternunquoted auf dem RHS.
Charles Duffy
@CharlesDuffy, das war der Punkt, der in den Fragen und Antworten angesprochen wurde, auf den sich diese Frage bezieht .
Stéphane Chazelas
Ahh - der Kontext macht Sinn; und Ihre Antwort hier ist hervorragend. Vielen Dank in beiden Punkten.
Charles Duffy
Tim, hat eine der folgenden Antworten deine Frage beantwortet? Bitte überlegen Sie, ob Sie dies akzeptieren möchten. Vielen Dank!
Jeff Schaller

Antworten:

13

Es gibt keinen guten Grund warum

[[ $a = a|b ]]

Sollte einen Fehler melden, anstatt zu testen, ob $ a die a|bZeichenfolge ist[[ $a =~ a|b ]] kein Fehler zurückgegeben wird.

Der einzige Grund ist, dass |im Allgemeinen (außen und innen [[ ... ]]) ein besonderer Charakter ist. Erwarten Sie an dieser [[ $a =Stelle basheinen Tokentyp, der ein normales WORT ist, wie die Argumente oder Ziele von Umleitungen in einer normalen Shell-Befehlszeile (aber so, als ob die extglobOption seit Bash 4.1 aktiviert worden wäre).

(Mit WORD beziehe ich mich hier auf ein Wort in einer hypothetischen Shell-Grammatik, wie sie in der POSIX-Spezifikation beschrieben ist. Dies ist etwas, das die Shell als ein Token in einer einfachen Shell-Befehlszeile parsen würde, nicht als andere Definition von Wörtern wie dem Englischen eine aus einer Folge von Buchstaben oder eine Sequenz von nicht-Abstandszeichen. foo"bar baz", $(echo x y)sind zwei solcher WORD s).

In einer normalen Shell-Befehlszeile:

echo a|b

Wird echo azu geleitet b. a|bist kein WORT , es sind drei Token: ein a WORT , ein |Token und ein b WORT- Token.

Wenn in verwendet [[ $a = a|b ]], bashrechnet mit einem WORD , die es bekommt ( a), aber dann findet ein unerwartetes |Token , das den Fehler verursacht.

Interessanterweise bashbeschwert sich nicht in:

[[ $a = a||b ]]

Da es sich jetzt um ein aToken handelt, auf das ein ||Token folgt b, wird es folgendermaßen analysiert:

[[ $a = a || b ]]

Was testet das $aist aoder dass die bZeichenfolge nicht leer ist.

Jetzt in:

[[ $a =~ a|b ]]

bashkann nicht die gleiche Parsing-Regel haben. Dieselbe Parsing-Regel zu haben würde bedeuten, dass das oben Genannte einen Fehler ergibt und dass man dies zitieren muss, |um sicherzustellen, dass es sich um a|bein einziges WORT handelt . Aber seit Bash 3.2, wenn Sie tun:

[[ $a =~ 'a|b' ]]

Das a|bpasst nicht mehr zum regulären Ausdruck, sondern zum a\|bregulären Ausdruck. Das heißt, Shell-Anführungszeichen haben den Nebeneffekt, dass die spezielle Bedeutung von regulären Ausdrücken entfernt wird. Es ist eine Funktion, daher ähnelt das Verhalten [[ $a = "?" ]]demjenigen, aber Platzhaltermuster (verwendet in [[ $a = pattern ]]) sind Shell- WÖRTER (verwendet zum Beispiel in Globs), während reguläre Ausdrücke dies nicht tun.

So müssen bashalle erweiterten Regexp-Operatoren, die ansonsten normalerweise spezielle Shell-Zeichen |sind (, )unterschiedlich behandelt werden, wenn ein Argument der=~ Operators .

Beachten Sie jedoch, dass während

 [[ $a =~ (ab)*c ]]

jetzt arbeitet,

 [[ $a =~ [)}] ]]

nicht. Du brauchst:

 [[ $a =~ [\)}] ]]
 [[ $a =~ [')'}] ]]

Welche in früheren Versionen von bashwürde falsch auf Backslash passen. Das war behoben, aber

 [[ $a =~ [^]')'] ]]

Stimmt nicht mit Backslash überein, wie es zum Beispiel sein sollte. Da sich das bashnicht in )den Klammern befindet, entgeht das ), was zu einem [^]\)]regulären Ausdruck führt, der für jedes Zeichen außer ], \und passt ).

ksh93 hat viel schlimmere Bugs an dieser Front.

In zsh, es ist eine normale Shell Wort , das erwartet wird , und unter Angabe regexp Betreiber nicht die Bedeutung von regexp Operatoren beeinflussen.

[[ $a =~ 'a|b' ]]

Stimmt mit dem a|bregulären Ausdruck überein.

Das heißt, das =~kann auch zum [/ testBefehl hinzugefügt werden :

[ "$a" '=~' 'a|b' ]
test "$a" '=~' 'a|b'

(auch arbeiten in yash. Die =~muss dort zshals =somethingspezieller Shell-Operator angegeben werden).

Bash 3.1 verwendet, um sich wie zu verhalten zsh. Es wurde in Version 3.2 geändert, vermutlich, um eine Ausrichtung mit ksh93(obwohl bashes sich um die erste Shell handelte [[ =~ ]]), aber Sie können es trotzdem tun BASH_COMPAT=31oder shopt -s compat31zum vorherigen Verhalten zurückkehren (mit der Ausnahme, dass [[ $a =~ a|b ]]in bashVersion 3.1 zwar ein Fehler zurückgegeben wird , dies jedoch nicht mehr der Fall ist in bash -O compat31neueren Versionen von bash).

Ich hoffe, es wird klargestellt, warum ich sagte, dass die Regeln verwirrend waren und warum:

[[ $a =~ $var ]]

Hilft auch bei der Portabilität auf andere Shells.

Stéphane Chazelas
quelle
zsh meldet außerdem einen Fehler am [[ $a = a|b ]].
Isaac
@isaac, ja, das ist der Punkt, den ich hier mache. a|bist kein Shell WORD hier, es ist das a, |und bToken. Wie echo a|bnicht ausgibt a|boder sich nicht ausdehnt , eine a|bglob, müssen Sie das zitieren , |da es eine spezielle Shell - Charakter ist , die in diesem Zusammenhang ist ungültig. [[ $a = (a|b) ]]würde funktionieren wie echo (a|b)würde funktionieren wie (a|b)ein zsh-Wildcard-Operator.
Stéphane Chazelas
Der Wortlaut und die Erklärung Ihrer Antwort enthalten nur den Namen bash. Das ist nicht die ganze Wahrheit.
Isaac
11

Standard Klackse ( "Dateiname Erweiterung") sind: *, ?und [ ... ].|ist in den Standardeinstellungen (non-extglob) kein gültiger Glob-Operator.

Versuchen:

shopt -s extglob
[[ a = @(a|b) ]] && echo matched
Jeff Schaller
quelle
1
Vielen Dank. Aber warum ist es nicht |buchstäblich inteperiert? Warum liegt ein Syntaxfehler vor?
StackExchange for All
1
Es wurde nicht zitiert.
Jeff Schaller
3
|Ist ein Glob-Operator in der Standardeinstellung nicht so, dass er nicht |ohne Anführungszeichen wörtlich interpretiert wird? Warum liegt also ein Syntaxfehler vor?
StackExchange for All
1
|ist ein Steuerzeichen; Es wird niemals wie ein Buchstabe oder eine Zahl als buchstäbliches Zeichen behandelt.
Chepner
3
Weil die Shell in diesem Modus kein Pipe-Redirect-Zeichen in der Mitte eines noch nicht geschlossenen [[]] erwartet hat. [[ $a = aist kein gültiger Befehl, dessen Ausgabe an einen anderen Prozess weitergeleitet werden kann (zumindest dachte die Shell, dass Sie das versucht haben).
Jason C
5

Wenn Sie eine Regex-Übereinstimmung wünschen, wäre der Test:

[[ "$a" =~ a|b ]]
Todesgriff
quelle
@Tim Sie sollten neue Fragen öffnen und Ihre aktuelle Frage nicht kontinuierlich bearbeiten.
Gardenhead
@gardenhead: Mein Update dient dazu, meine Fragen zu klären, anstatt sie zu ändern, falls Sie sie verpassen. Der zweite Teil, den ich hinzugefügt habe, besteht darin, die Pipe-Erklärung eines Kommentars zu meiner ursprünglichen Frage (warum der Syntaxfehler nicht korrekt ist) anzuzeigen.
StackExchange for All