Wie unterscheidet bash zwischen Klammererweiterung und Befehlsgruppierung?

47

Mir ist aufgefallen, dass {bei der Klammererweiterung verwendet werden kann:

echo {1..8}

oder in Befehlsgruppierung:

{ls;echo hi}

Woher kennt bash den Unterschied?

Liebesfeder
quelle
1
Ausgezeichnete Frage, +1. Es sieht so aus, {als würde es als Befehlsliste interpretiert, wenn es zu Beginn eines Befehls erscheint, und als geschweifte Erweiterung, aber ich bin mir nicht sicher.
Celada
16
{ls;echo hi}ist nicht legal bash. Sie benötigen nach der öffnenden Klammer ein Leerzeichen und vor der schließenden ein Semikolon.
PSkocik

Antworten:

38

Ein vereinfachtes Grund ist die Existenz eines Zeichens: space.

Klammererweiterungen verarbeiten keine (nicht in Anführungszeichen gesetzten) Leerzeichen.

Eine {...}Liste benötigt (nicht in Anführungszeichen gesetzte) Leerzeichen.

Die detailliertere Antwort lautet, wie die Shell eine Befehlszeile analysiert .


Der erste Schritt zum Parsen (Verstehen) einer Befehlszeile besteht darin, sie in Teile zu unterteilen.
Diese Teile (normalerweise als Wörter oder Token bezeichnet) ergeben sich aus der Unterteilung einer Befehlszeile an jedem Metazeichen aus dem Link :

  1. Teilt den Befehl in Token auf, die durch die festgelegten Metazeichen getrennt sind: SPACE, TAB, NEWLINE,;, (,), <,>, | und &. Zu den Tokentypen gehören Wörter, Schlüsselwörter, E / A-Umleitungen und Semikolons.

Metazeichen: spacetabenter;,<>|und &.

Nach dem Teilen können Wörter von einem Typ sein (wie von der Shell verstanden):

  • Befehlsvorbelegungen: LC=ALL ...
  • Befehl LC=ALL echo
  • Argumente LC=ALL echo "hello"
  • Umleitung LC=ALL echo "hello" >&2

Klammererweiterung

Nur wenn eine "geschweifte Klammer" (ohne Leerzeichen oder Metazeichen) ein einzelnes Wort ist (wie oben beschrieben) und nicht in Anführungszeichen steht , ist sie ein Kandidat für die "geschweifte Klammer". Weitere Überprüfungen der internen Struktur werden später durchgeführt.

Also: {ls,-l}Qualifiziert sich als "Klammererweiterung", um ls -lentweder als first wordoder argument(in bash ist zsh anders) zu werden.

$ {ls,-l}            ### executes `ls -l`
$ echo {ls,-l}       ### prints `ls -l`

Aber das wird nicht: {ls ,-l}. Bash teilt spacedie Zeile in zwei Wörter auf und parst sie: {lsund ,-l}das löst ein aus command not found(das Argument ,-l}geht verloren):

 $ {ls ,-l}
 bash: {ls: command not found

Ihre Zeile: {ls;echo hi}wird aufgrund der beiden Metazeichen ;und nicht zu einer "Klammererweiterung" space.

Es wird in diese drei Teile unterteilt werden: {lsneuen Befehl: echo hi}. Verstehen Sie, dass dies den ;Start eines neuen Befehls auslöst. Der Befehl {lswird nicht gefunden und der nächste Befehl wird ausgegeben hi}:

$ {ls;echo hi}
bash: {ls: command not found
hi}

Wenn es nach einem anderen Befehl platziert wird, startet es trotzdem einen neuen Befehl nach dem ;:

$ echo {ls;echo hi}
{ls
hi}

Liste

Einer der „Verbindung Befehle“ ist eine „Brace List“ (meine Worte): { list; }.
Wie Sie sehen, wird es mit Leerzeichen und einem Abschluss definiert ;.
Die Leerzeichen und ;werden benötigt, da beide {und }"Reserved Words " sind.

Um als Wörter erkannt zu werden, müssen sie daher von Metazeichen umgeben sein (fast immer:) space.

Wie in Punkt 2 der verlinkten Seite beschrieben

  1. Überprüft das erste Token jedes Befehls, um festzustellen, ob es sich um ...., {oder (handelt. Der Befehl ist dann tatsächlich ein zusammengesetzter Befehl.

Ihr Beispiel: {ls;echo hi}ist keine Liste.

Es braucht einen Schluss ;und ein Leerzeichen (mindestens) danach {. Der letzte }wird durch den Abschluss definiert ;.

Dies ist eine Liste { ls;echo hi; }. Und das { ls;echo hi;}ist auch (seltener verwendet, aber gültig) (Danke @choroba für die Hilfe).

$ { ls;echo hi; }
A-list-of-files
hi

Als Argument (die Shell kennt den Unterschied) für einen Befehl löst sie jedoch einen Fehler aus:

$ echo { ls;echo hi; }
bash: syntax error near unexpected token `}'

Aber seien Sie vorsichtig, wenn Sie glauben, dass die Shell analysiert:

$ echo { ls;echo hi;
{ ls
hi

quelle
2
Das ist wirklich die beste Antwort, denn Sie geben uns in der Tat, wie Bash-Parser funktioniert! und mit einer ausführlichen Erklärung!
Lovespring
2
Sie brauchen keinen Abstand zwischen ;und }. { ls;}funktioniert, da das Semikolon bereits ein Metazeichen ist.
Choroba
1
@lovespring Danke, ja, ich habe einige Zeit in das Schreiben investiert. Ich bin froh zu wissen, dass es nützlich ist. Noch einmal Danke.
ausgezeichneter Artikel, vielen Dank für Verweise
Edward Torvalds
16

Der Block {ist ein Shell-Schlüsselwort, daher muss er durch ein Leerzeichen vom nächsten Wort getrennt werden, während bei der Klammererweiterung kein Leerzeichen vorhanden sein darf (wenn Sie ein Leerzeichen in Klammern setzen müssen, müssen Sie es maskieren:) echo {\ ,a}{b,c}.

Sie können die geschweifte Klammer zu Beginn eines Befehls verwenden:

{ls,.}  # expands to "ls ."

Sie können es jedoch nicht zum Erweitern eines Blocks verwenden, da das Parsen der Gruppierungsbefehle vor den Erweiterungen erfolgt:

echo {'{ ls','.;}'}  # { ls .;}
{'{ ls','.;}'}       # bash: { ls: No such file or directory
Choroba
quelle
5

Es weiß durch Überprüfen der Syntax der Befehlszeile. Ebenso ist bekannt, dass im Ausdruck echo echodas erste Echo als Befehl und das zweite Echo als Parameter des ersten Echos behandelt werden soll.

In bash ist es sehr einfach, da { cmd; }Leerzeichen und Semikolon enthalten sein sollten. Zum Beispiel in zsh werden sie jedoch nicht benötigt, aber durch die Analyse des {}Shell- Kontexts kann festgestellt werden, was mit dem Inhalt geschehen soll.

Folgendes berücksichtigen:

alias 1..3=date
{ 1..3; }    #in bash
{1..3}       #in zsh

Beide geben das aktuelle Datum zurück, aber

echo {1..3}

Gibt zurück, 1 2 3weil die Shell {}ein Argument für den Befehl kennt echound es daher erweitert werden sollte.

jimmij
quelle
{gefolgt von nicht angegebenem Leerzeichen wird die Erweiterung der Klammern in Bash nicht gestartet.
Choroba
@choroba Ja, und nicht nur gleich danach {. Leerzeichen ohne Anführungszeichen können nirgendwo stehen, da die Shell die gesamte Befehlszeile in Leerzeichen aufteilt.
Jimmy
0

Erstens muss die zusammengesetzte Klammer ein Wort für sich und das erste Wort der Befehlszeile sein:

echo { these braces are just words }

Zweitens sind einzelne Zahnspangen nichts Besonderes (wie Sie oben sehen können). Leere Klammern sind auch nichts Besonderes:

echo {} # just the token {}: familiar from the find command

Alles ohne Komma ist auch nur sich selbst

echo {abc} # just {abc}

Hier beginnt die Aktion.

echo {a,b} # braces disappear, a b results.

Um die Klammererweiterung zu aktivieren, benötigen wir ein einzelnes Wort (nicht in Felder auf Leerzeichen getrennt), in dem mindestens eine Instanz {...}vorkommt, in der mindestens ein Komma vorkommt.

Dies kann übrigens das erste Wort in der Befehlszeile sein:

{ls,-l} .   # just "ls -l ."
Kaz
quelle