Warum wird {1,2}, das von einem Befehl in $ () gedruckt wird, nicht interpoliert?

8

Ich bin in einem Verzeichnis, in dem ich zwei Textdateien habe:

$ touch test1.txt
$ touch test2.txt

Wenn ich versuche, die Dateien (mit Bash) anhand eines Musters aufzulisten, funktioniert dies:

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

Wenn jedoch ein Muster durch einen darin enthaltenen Befehl erzeugt wird $(), funktioniert nur eines der Muster:

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

Was ist denn hier los? Warum {1,2}funktioniert das Muster nicht?

Herosław Miraszewski
quelle
4
Klammererweiterung
3
@SergiyKolodyazhnyy Der Punkt der Frage ist , dass das ?wird auch zitiert, und wird nach dem Expandieren $(...)Ersatz es, aber die Expansion Klammer nicht.
Michael Homer
1
@muru Nein, das ist nicht das gleiche Problem. Hier spielt die Reihenfolge der Erweiterungen keine Rolle, es kommt darauf an, welche Erweiterung in welchem ​​Kontext stattfindet. Ich wäre nicht überrascht, wenn diese Frage ein Duplikat wäre, aber ich konnte sie nicht finden.
Gilles 'SO - hör auf böse zu sein'
1
@mosvy Ksh und bash führen Erweiterungen in derselben Reihenfolge durch, aber ksh klammert die Erweiterung in einem Fall ab, in dem bash dies überhaupt nicht tut. Zsh-with-globsubst führt die gleichen Erweiterungen wie bash durch, jedoch in einer anderen Reihenfolge.
Gilles 'SO - hör auf böse zu sein'
1
@ Gilles nein, tun sie nicht. Wie dokumentiert und leicht zu demonstrieren, führt ksh (und zsh) die Klammererweiterung unmittelbar vor dem Globbing durch. zsh-with-globsubst führt bei den Ergebnissen von $-expansions überhaupt keine Klammererweiterung durch : zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'.
Mosvy

Antworten:

17

Es ist eine Kombination aus zwei Dingen. Erstens ist die Klammererweiterung kein Muster, das mit Dateinamen übereinstimmt: Es handelt sich um eine rein textuelle Ersetzung - siehe Was ist der Unterschied zwischen "a [bc] d" (Klammern) und "a {b, c} d" (Klammern)? . Zweitens, wenn Sie das Ergebnis einer Befehlsersetzung außerhalb von doppelten Anführungszeichen ( ls $(…)) verwenden, geschieht nur ein Mustervergleich (und eine Wortaufteilung: der Operator „split + glob“), keine vollständige Neuanalyse.

Mit ls $(echo 'test?.txt')gibt der Befehl echo 'test?.txt'die Zeichenfolge aus test?.txt(mit einem letzten Zeilenumbruch). Die Befehlsersetzung führt zu der Zeichenfolge test?.txt(ohne letzte neue Zeile, da die Befehlsersetzung nachfolgende Zeilenumbrüche entfernt). Diese nicht zitierte Ersetzung wird einer Wortaufteilung unterzogen, wodurch eine Liste erhalten wird, die aus der einzelnen Zeichenfolge besteht, test?.txtda keine Leerzeichen (genauer gesagt keine Zeichen $IFS) darin enthalten sind. Jedes Element dieser Ein-Element-Liste wird dann einer bedingten Platzhaltererweiterung unterzogen. Da ?die Zeichenfolge ein Platzhalterzeichen enthält, erfolgt die Platzhaltererweiterung. Da das Muster test?.txtmit mindestens einem Dateinamen übereinstimmt, wird das Listenelement test?.txtdurch die Liste der Dateinamen ersetzt, die mit den Mustern übereinstimmen, wodurch die Liste mit zwei Elementen erhalten wirdtest1.txtund test2.txt. Schließlich lswird mit zwei Argumenten test1und aufgerufen test2.

Mit ls $(echo 'test{1,2}')gibt der Befehl echo 'test{1,2}'die Zeichenfolge aus test{1,2}(mit einem letzten Zeilenumbruch). Die Befehlssubstitution führt zur Zeichenfolge test{1,2}. Diese nicht zitierte Substitution wird einer Wortaufteilung unterzogen, wodurch eine Liste erhalten wird, die aus der einzelnen Zeichenfolge besteht test{1,2}. Jedes Element dieser Ein-Element-Liste wird dann einer bedingten Platzhaltererweiterung unterzogen, die nichts bewirkt (das Element bleibt unverändert), da die Zeichenfolge kein Platzhalterzeichen enthält. So lswird mit dem einzigen Argument aufgerufen test{1,2}.

Zum Vergleich ist hier, was passiert mit ls $(echo test{1,2}). Der Befehl echo test{1,2}gibt die Zeichenfolge aus test1 test2(mit einem letzten Zeilenumbruch). Die Befehlssubstitution führt zur Zeichenfolge test1 test2(ohne letzten Zeilenumbruch). Diese nicht zitierte Substitution wird einer Wortaufteilung unterzogen, wobei zwei Zeichenfolgen test1und erhalten werden test2. Da keine der Zeichenfolgen ein Platzhalterzeichen enthält, werden sie in Ruhe gelassen und daher lsmit zwei Argumenten test1und aufgerufen test2.

Gilles 'SO - hör auf böse zu sein'
quelle
3
Beachten Sie, dass pdksh und ksh93 bei Erweiterungen eine Klammererweiterung durchführen (vor dem Globbing; nicht mit Noglob, aber im Fall von ksh93 immer noch, wenn die Klammererweiterung ausgeschaltet ist!)
Stéphane Chazelas
Sie scheinen .txtin der zweiten Erklärung vergessen zu haben .
Val sagt Reinstate Monica
10

Die Reihenfolge der Erweiterungen lautet: Klammererweiterung; Tilde-Erweiterung, Parameter- und Variablenerweiterung, arithmetische Erweiterung und Befehlssubstitution (von links nach rechts); Wortaufteilung; und Dateinamenerweiterung.

Die Erweiterung der Klammer erfolgt nach dem Ersetzen des Befehls nicht. Mit eval können Sie eine weitere Erweiterungsrunde erzwingen:

eval echo $(echo '{1,2}lala')

Das Ergebnis ist:

1lala 2lala
dedowsdi
quelle
6

Dieses Problem ist sehr spezifisch bashund liegt daran, dass sie beschlossen haben bash, die Klammererweiterung von der Dateinamenerweiterung (Globbing) zu trennen und sie zuerst durchzuführen, bevor alle anderen Erweiterungen durchgeführt werden.

Aus der bashManpage:

Die Reihenfolge der Erweiterungen lautet: Klammererweiterung; Tilde-Erweiterung, Parameter- und Variablenerweiterung, arithmetische Erweiterung und Befehlssubstitution (von links nach rechts); Wortaufteilung; und Pfadnamenerweiterung.

In Ihrem Beispiel bashwerden Ihre geschweiften Klammern erst angezeigt, nachdem die Befehlsersetzung (the $(echo ...)) durchgeführt wurde, wenn es einfach zu spät ist.

Dies unterscheidet sich von allen anderen Shells, die die Klammererweiterung kurz vor (und einige sogar als Teil davon) der Pfadnamenerweiterung (Globbing) durchführen. Dies schließt ein, ist aber nicht darauf beschränkt, cshwo Klammererweiterungen zuerst erfunden wurden.

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

Das letztere Beispiel ist die gleiche in csh, zsh, ksh93, mkshoder fish.

Beachten Sie auch , dass die Klammererweiterung als Teil des Globbing auch über die glob(3)Bibliotheksfunktion (zumindest unter Linux und allen BSDs) und in anderen unabhängigen Implementierungen (z. B. in Perl :) verfügbar ist perl -le 'print join " ", <test{1,2}.txt>'.

Warum das anders gemacht wurde, bashhat wahrscheinlich eine Geschichte dahinter, aber FWIW Ich konnte keine logische Erklärung finden, und ich finde alle post-hoc-Rationalisierungen nicht überzeugend.

Mosvy
quelle
3
Beachten Sie, dass perlfrüher cshaufgerufen wurde, um Globs zu erweitern. Es ist daher nicht verwunderlich, dass immer noch dieselben Globbing-Operatoren erkannt werden wiecsh
Stéphane Chazelas,
1

Bitte versuche:::

ls $ (Echotest {1,2} \. txt)

Mit einem BackSlash. Es funktioniert jetzt. Entfernen Sie auch die wie im vorherigen Poster genannten Anführungszeichen. Der Punkt dient nicht zum Abgleichen von Mustern, sondern ist hier wörtlich als Punkt zu verstehen.

mkzia
quelle
(1) Die Frage lautet: „Was ist hier los? Warum verhält sich das Muster so {1,2}wie es ist? Die Frage lautet nicht: "Wie kann ich einen Befehl erhalten, mit {1,2}dem ich mich so verhalte, wie der Befehl ?funktioniert?" Sie beantworten die falsche Frage. (2) Der Backslash hat nichts damit zu tun. Ihr Befehl funktioniert so, wie Sie es tun, weil Sie die Anführungszeichen entfernt haben, die im Befehl in der Frage enthalten waren.
G-Man sagt 'Reinstate Monica'
0

Es funktioniert, wenn Sie die Anführungszeichen entfernen

$ ls $(echo test{1,2})
test1  test2
Darxmurf
quelle
9
Die Erweiterung findet jetzt vor der Befehlsersetzung statt, was meiner Meinung nach nicht die Frage ist (vergleiche, was passiert ?).
Michael Homer