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?.txt
da 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?.txt
mindestens einem Dateinamen übereinstimmt, wird das Listenelement test?.txt
durch die Liste der Dateinamen ersetzt, die mit den Mustern übereinstimmen, wodurch die zweielementige Liste entsteht, die test1.txt
und enthält test2.txt
. Schließlich ls
wird mit zwei Argumenten test1
und 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 ls
wird 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 test1
und entstehen test2
. Da keiner der Strings ein Platzhalterzeichen enthält, bleiben sie unverändert und werden daher mit zwei Argumenten und ls
aufgerufen .test1
test2
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 bash
und liegt daran, dass in entschieden wurde, bash
die Klammernerweiterung von der Dateinamenerweiterung (Globbing) zu trennen und sie vor allen anderen Erweiterungen auszuführen.
Aus der bash
Manpage:
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 bash
werden 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 csh
denen 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
csh
Das letzte Beispiel ist in , zsh
, ksh93
, mksh
oder 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, bash
hat 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.