Wie werden doppelte Anführungszeichen in Bash verglichen (gepaart)?

15

Ich benutze GNU bash 4.3.48. Betrachten Sie die folgenden zwei Befehle, die sich nur durch ein einzelnes Dollarzeichen unterscheiden.

Befehl 1:

echo "(echo " * ")"

Befehl 2:

echo "$(echo " * ")"

Die Ausgabe von ihnen ist jeweils

(echo  test.txt ppcg.sh )

und

 * 

Im ersten Fall *ist das also offensichtlich globbed, was bedeutet, dass das erste Anführungszeichen mit dem zweiten ein Paar bildet und das dritte und das vierte ein weiteres Paar bilden.

Im zweiten Fall *ist das nicht globbed, und es gibt genau zwei zusätzliche Leerzeichen in der Ausgabe, eins vor dem Sternchen und eins nach dem Sternchen, was bedeutet, dass das zweite Anführungszeichen mit dem dritten und das erste mit dem vierten übereinstimmt.

Gibt es außer der $()Konstruktion noch andere Fälle , in denen die Anführungszeichen nicht mit dem nächsten übereinstimmen, sondern stattdessen verschachtelt sind? Ist dieses Verhalten gut dokumentiert und wenn ja, wo finde ich das entsprechende Dokument?

Weijun Zhou
quelle
Auch hier ist das Syntax-Highlight in UL SE falsch und irreführend, obwohl JS die Schuld trägt.
Weijun Zhou

Antworten:

14

Jedes der Verschachtelungskonstrukte, die in Zeichenfolgen interpoliert werden können, kann weitere Zeichenfolgen enthalten: Sie werden wie ein neues Skript bis zur abschließenden Markierung analysiert und können sogar mehrere Ebenen tief verschachtelt werden. Alle Balken beginnen mit einem $. Alle von ihnen sind in einer Kombination aus Bash-Handbuch und POSIX-Shell-Befehlssprachenspezifikation dokumentiert.

Es gibt einige Fälle dieser Konstrukte:

  • Befehlsersetzung mit$( ... ) , wie Sie gefunden haben. POSIX gibt dieses Verhalten an :

    Mit dem $(command)Formular bilden alle Zeichen, die auf die offene Klammer bis zur passenden schließenden Klammer folgen, den Befehl. Für den Befehl kann ein beliebiges gültiges Shell-Skript verwendet werden ...

    Anführungszeichen sind Teil gültiger Shell-Skripte, daher sind sie mit ihrer normalen Bedeutung zulässig.

  • Befehlssubstitution auch mit `.
  • Das "Wort" -Element von Instanzen${parameter:-word} mit erweiterter Parametersubstitution wie z . Die Definition von "Wort" ist :

    Eine Folge von Zeichen, die von der Shell als Einheit behandelt werden

    - der zitierten Text und sogar gemischte Zitate enthält a"b"c'd'e- obwohl das tatsächliche Verhalten der Erweiterungen etwas liberaler ist und zum Beispiel auch ${x:-hello world}funktioniert.

  • Arithmetische Erweiterung mit $(( ... )), obwohl es dort weitgehend nutzlos ist (aber Sie können auch Befehlssubstitutionen oder variable Erweiterungen verschachteln und dann sinnvollerweise Anführungszeichen in diesen verwenden). POSIX gibt an, dass :

    Der Ausdruck wird wie in doppelten Anführungszeichen behandelt, mit der Ausnahme, dass ein doppeltes Anführungszeichen innerhalb des Ausdrucks nicht speziell behandelt wird. Die Shell muss alle Token im Ausdruck für die Parametererweiterung, die Befehlssubstitution und die Entfernung von Anführungszeichen erweitern.

    Daher ist dieses Verhalten ausdrücklich erforderlich. Das heißt echo "abc $((4 "*" 5))", arithmetisch, anstatt zu klackern.

    Beachten Sie jedoch, dass $[ ... ]arithmetische Erweiterungen alten Stils nicht auf die gleiche Weise behandelt werden: Anführungszeichen sind ein Fehler, wenn sie angezeigt werden, unabhängig davon, ob die Erweiterung in Anführungszeichen steht oder nicht. Dieses Formular ist nicht mehr dokumentiert und soll auch nicht mehr verwendet werden.

  • Gebietsschemaspezifische Übersetzung mit$"..." , die eigentlich das "als Kernelement verwendet. $"wird als eine Einheit behandelt.

Es gibt noch einen weiteren Verschachtelungsfall, den Sie möglicherweise nicht erwarten und der keine Anführungszeichen enthält, nämlich die Erweiterung in{a,b{c,d},e} geschweifte Klammern : Wird zu "a bc bd e" erweitert. ${x:-a{b,c}d}tut nicht nisten, jedoch; Es wird als Parametersubstitution behandelt, die " a{b,c" gefolgt von " d}" ergibt . Das ist auch dokumentiert :

Wenn geschweifte Klammern verwendet werden, ist die passende endende geschweifte Klammer das erste '}', das nicht durch einen Backslash oder innerhalb einer Zeichenfolge in Anführungszeichen und nicht innerhalb einer eingebetteten arithmetischen Erweiterung, Befehlssubstitution oder Parametererweiterung maskiert wird.


In der Regel analysieren alle abgegrenzten Konstrukte ihre Körper unabhängig vom umgebenden Kontext (und Ausnahmen werden als Bugs behandelt ). $(Wenn der Befehlssubstitutionscode angezeigt wird, fordert er den Parser im Wesentlichen auf, so viel wie möglich aus dem Body zu konsumieren, als wäre es ein neues Programm. Anschließend wird überprüft, ob der erwartete Abschlussmarker (ein nicht entkapptes )oder ))oder }) angezeigt wird, sobald der Subparser ausgeführt wird von Dingen, die es verbrauchen kann.

Wenn Sie über die Funktionsweise eines Parsers für rekursive Abstiegsdaten nachdenken , ist dies nur eine einfache Rekursion zum Basisfall. Es ist tatsächlich einfacher als andersrum, wenn Sie überhaupt eine String-Interpolation haben. Unabhängig von der zugrunde liegenden Analysetechnik führen Shells, die diese Konstrukte unterstützen, zu demselben Ergebnis.

Durch diese Konstrukte können Sie das Zitieren so tief verschachteln, wie Sie möchten, und es funktioniert wie erwartet. Nirgendwo wird es verwirrend sein, ein Zitat in der Mitte zu sehen. Stattdessen ist dies der Beginn einer neuen Zeichenfolge in Anführungszeichen im inneren Kontext.

Michael Homer
quelle
Vielen Dank. In "blah/blah\n$(cat "${tmpdir}/${filename}.jpdf")", warum ist die zweiten doppelten Anführungszeichen nicht das Ende der ersten doppelten Anführungszeichen (wie durch die Syntax gezeigt in Ihrer Antwort hervorgehoben), aber ein Anfang einer Zeichenfolge innerhalb $(...)? Liegt es daran, dass der Bash-Parser von oben nach unten und nicht von unten nach oben ist?
Tim
2
Die Handhabung von ist sehr unterschiedlich "${var-"foo"}"( echo "${-+"*"}"wie zum Beispiel echo *in der Bourne- oder Korn-Shell), und das Verhalten wird in der nächsten Version des Standards deutlich unspezifiziert . Siehe auch Diskussion unter mail-archive.com/[email protected]/msg00167.html
Stéphane Chazelas
3

Vielleicht hilft ein Blick auf die beiden Beispiele mit printf(anstelle von echo):

$ printf '<%s> ' "(echo " * ")"; echo
<(echo > <test.txt> <ppcg.sh> <file1> <file2> <file3> <)>

Es werden (echo (das erste Wort, einschließlich eines nachgestellten Leerzeichens), einige Dateien und das Schlusswort gedruckt ).
Die Klammer ist nur ein Teil der in Anführungszeichen gesetzten Zeichenfolge (echo .
Das Sternchen (jetzt ohne Anführungszeichen, da die beiden Anführungszeichen gepaart sind) wird als Glob zu einer Liste übereinstimmender Dateien erweitert.
Und dann die schließende Klammer.

Ihr zweiter Befehl funktioniert jedoch wie folgt:

$ printf '<%s> ' "$(echo " * ")" ; echo
< * >

Das $startet eine Befehlsersetzung. Das fängt ganz frisch an zu zitieren.
Das Sternchen ist in Anführungszeichen gesetzt, " * "und das ist, was der Befehl (hier ist es ein Befehl und keine in Anführungszeichen gesetzte Zeichenfolge) echoausgibt. Zum Schluss printfformatieren Sie das neu *und drucken es als < * >.

Isaac
quelle