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

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

Ich befinde mich 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 es:

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

Wenn jedoch ein Muster durch einen in eingeschlossenen Befehl erstellt 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 hier los? Warum {1,2}funktioniert das Muster nicht?

Antwort1

Es ist eine Kombination aus zwei Dingen. Erstens ist die Klammererweiterung kein Muster, das Dateinamen entspricht: Es handelt sich um eine rein textuelle Ersetzung – sieheWas ist der Unterschied zwischen ‚a[bc]d‘ (Klammern) und ‚a{b,c}d‘ (Klammern)?. Zweitens: Wenn Sie das Ergebnis einer Befehlsersetzung außerhalb von Anführungszeichen ( ls $(…)) verwenden, erfolgt lediglich ein Mustervergleich (und eine Worttrennung: der Operator „split+glob“), keine vollständige erneute Analyse.

Mit gibt ls $(echo 'test?.txt')der Befehl echo 'test?.txt'den String aus test?.txt(mit einem abschließenden Zeilenumbruch). Die Befehlsersetzung ergibt den String test?.txt(ohne abschließenden Zeilenumbruch, da die Befehlsersetzung nachfolgende Zeilenumbrüche entfernt). Diese Ersetzung ohne Anführungszeichen wird einer Wortaufteilung unterzogen, wodurch eine Liste entsteht, die aus dem einzelnen String besteht, test?.txtda dieser keine Leerzeichen (genauer gesagt, keine Zeichen in $IFS) enthält. Jedes Element dieser einelementigen Liste wird dann einer bedingten Platzhaltererweiterung unterzogen, und da ?der String ein Platzhalterzeichen enthält, wird die Platzhaltererweiterung tatsächlich durchgeführt. Da das Muster mit test?.txtmindestens einem Dateinamen übereinstimmt, wird das Listenelement test?.txtdurch die Liste der Dateinamen ersetzt, die mit den Mustern übereinstimmen, wodurch die zweielementige Liste entsteht, die test1.txtund enthält test2.txt. Schließlich lswird mit zwei Argumenten test1und aufgerufen test2.

Mit gibt ls $(echo 'test{1,2}')der Befehl echo 'test{1,2}'die Zeichenfolge aus test{1,2}(mit einem abschließenden Zeilenumbruch). Die Befehlsersetzung ergibt die Zeichenfolge test{1,2}. Diese nicht in Anführungszeichen gesetzte Ersetzung wird in Wörter aufgeteilt, wodurch eine Liste entsteht, die aus der einzelnen Zeichenfolge besteht test{1,2}. Jedes Element dieser einelementigen Liste wird dann einer bedingten Platzhaltererweiterung unterzogen, die nichts bewirkt (das Element bleibt unverändert), da die Zeichenfolge kein Platzhalterzeichen enthält. Daher lswird mit dem einzelnen Argument aufgerufen test{1,2}.

Zum Vergleich: Folgendes passiert mit ls $(echo test{1,2}). Der Befehl echo test{1,2}gibt den String aus test1 test2(mit einem abschließenden Zeilenumbruch). Die Befehlsersetzung ergibt den String test1 test2(ohne abschließenden Zeilenumbruch). Diese Ersetzung ohne Anführungszeichen wird einer Wortaufteilung unterzogen, wodurch zwei Strings test1und entstehen test2. Da keiner der Strings ein Platzhalterzeichen enthält, bleiben sie unverändert und werden daher mit zwei Argumenten und lsaufgerufen .test1test2

Antwort2

Die Reihenfolge der Erweiterungen ist: Klammernerweiterung, Tilde-Erweiterung, Parameter- und Variablenerweiterung, arithmetische Erweiterung und Befehlsersetzung (von links nach rechts), Worttrennung und Dateinamenerweiterung.

Nach der Befehlsersetzung wird keine Klammererweiterung durchgeführt. Sie können eval verwenden, um eine weitere Erweiterungsrunde zu erzwingen:

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

Das Ergebnis ist:

1lala 2lala

Antwort3

Dieses Problem ist ganz spezifisch für bashund liegt daran, dass in entschieden wurde, bashdie Klammernerweiterung von der Dateinamenerweiterung (Globbing) zu trennen und sie vor allen anderen Erweiterungen auszuführen.

Aus der bashManpage:

Die Reihenfolge der Erweiterungen ist: Klammernerweiterung, Tilde-Erweiterung, Parameter- und Variablenerweiterung, arithmetische Erweiterung und Befehlsersetzung (von links nach rechts durchgeführt), Worttrennung und Pfadnamenerweiterung.

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

Dies unterscheidet sich von allen anderen Shells, die die Klammererweiterung direkt vor (und manche sogar als Teil davon) der Pfadnamenerweiterung (Globbing) durchführen. Dies gilt unter anderem auch für die Bereiche, in cshdenen Klammererweiterungen erstmals 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

cshDas letzte Beispiel ist in , zsh, ksh93, mkshoder dasselbe fish.

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

Warum das anders gemacht wurde, bashhat wahrscheinlich eine Geschichte, aber ich konnte keine logische Erklärung finden und finde alle nachträglichen Rationalisierungen nicht überzeugend.

Antwort4

Bitte versuche:::

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

Mit einem BackSlash. Jetzt funktioniert es. Entfernen Sie auch die Anführungszeichen, wie der vorherige Poster sagte. Der Punkt dient nicht zum Anpassen des Musters, sondern ist hier wörtlich als Punkt zu verstehen.

verwandte Informationen