Festlegen von Bash-Optionen in einem zusammengesetzten Befehl

9

Ich habe festgestellt, dass das Setzen der extglobShell-Option innerhalb einer zusammengesetzten Verbindung zum Versagen nachfolgender Anti-Globs führt. Müssen Shell-Optionen außerhalb von zusammengesetzten Befehlen festgelegt werden? Ich habe in den Bash-Manpages keinen Hinweis auf eine solche Anforderung gesehen.

Das folgende Skript funktioniert beispielsweise einwandfrei (Drucke a.0 a.1):

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
shopt -s extglob
ls "a."!(b*)

Wenn die letzten beiden Zeilen jedoch als zusammengesetzter Befehl ausgeführt werden, schlägt das Skript mit dem folgenden Fehler fehl:

syntax error near unexpected token `('
`    ls "a."!(b*)'

Dies wurde mit Bash-Versionen von 4.2 bis 4.4 und mit einer Vielzahl von zusammengesetzten Befehlen getestet :

(1) bedingt - if

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
if true; then
    shopt -s extglob
    ls "a."!(b*)
fi

(2) Zahnspangen - { }

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
{
    shopt -s extglob
    ls "a."!(b*)
}

(3) Unterschale - ( ):

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
(
    shopt -s extglob
    ls "a."!(b*)
)

In allen Fällen shoptist das Skript erfolgreich , wenn der Befehl außerhalb des zusammengesetzten Befehls verschoben wird.

user001
quelle

Antworten:

14

Kein Teil eines zusammengesetzten Befehls wird ausgeführt, bevor der gesamte Befehl analysiert wurde. Dies ist im Abschnitt " Shell-Operation " des Handbuchs schräg dokumentiert : Alle Tokenisierungs- und Analysevorgänge werden ausgeführt, bevor "der" Befehl ausgeführt wird. Durch Aktivieren extglobwird die Sprachsyntax geändert, indem neue Operatoren für die Mustererkennung hinzugefügt werden, die während der Tokenisierung erkannt werden.

Da der shoptBefehl zum Zeitpunkt des !(Erreichens noch nicht ausgeführt wurde , wird er entweder als versuchte Verlaufserweiterung ( !) oder als Steuerungsoperator angesehen (und nicht als !(einzelnes Element (und dasselbe für @(usw., außer dass es keine Verlaufserweiterung gibt Dort).

Wenn das Parsen dieses Token erreicht, ist jeder Fall im Allgemeinen ein Fehler, obwohl !(...)am Anfang einer Zeile oder danach timeeine negierte Subshell-Pipeline steht. " unexpected token `('" bedeutet, dass an dieser Stelle kein Unterschalenausdruck akzeptiert werden konnte.

Dies ist etwas ärgerlich, wenn Extglob nur vorübergehend in einer Subshell aktiviert werden soll. Eine Abhilfe ist , eine Funktion zu definieren, verwendet das glob Sie wollen, dann wieder deaktivieren extglob, und dann rufen Sie die Funktion der Sub - Shell mit extglob aktiviert:

shopt -s extglob
f() { ls "a."!(b*) ; }
shopt -u extglob
(
    shopt -s extglob
    f
)

Es ist sehr klobig.


Der gleiche Effekt tritt auf, wenn Sie einen Alias ​​innerhalb eines zusammengesetzten Befehls erstellen, da diese auch vor dem Parsen verarbeitet werden:

if true
then
    alias foo=echo
    foo bar
fi
foo xyz

wird gedruckt foo: command not foundund dann xyz, weil der Alias erstellt wurde, aber erst verfügbar ist , wenn der Alias iffertig ist.

Michael Homer
quelle
Danke - Ich wusste nicht, dass zusammengesetzte Befehle vor der Ausführung als Einheit analysiert wurden. Das Verhalten macht jetzt Sinn.
user001
3
@ user001 Ich wäre nicht so gemeinnützig zu sagen, dass es Sinn macht. Das Umschalten des Lexer-Modus während des Parsens ist nichts Unbekanntes, und andere Sprachen mögen Coder perlkönnen es gut. Beachten Sie, dass es nicht ausreicht, nicht Teil eines zusammengesetzten Befehls zu sein, sondern sich shopt -s extglobauch in einer separaten Zeile befinden muss. Siehe auch die Diskussion und Beispiele hier
Mosvy
@mosvy: Ich meinte, dass das beobachtete Verhalten Sinn macht, wenn man merkt, dass bash einen zusammengesetzten Befehl als Einheit analysiert (nicht, dass die Einschränkungen des Parsers notwendigerweise vernünftig sind). Vielen Dank für den Link zur Diskussion im anderen Beitrag.
user001