Mengapa {1,2} dicetak oleh perintah dalam $ () tidak diinterpolasi?

8

Saya berada di direktori di mana saya memiliki dua file teks:

$ touch test1.txt
$ touch test2.txt

Ketika saya mencoba membuat daftar file (dengan Bash) menggunakan beberapa pola kerjanya:

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

Namun, ketika sebuah pola dihasilkan oleh perintah yang disertakan $(), hanya satu pola yang berfungsi:

$ 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

Apa yang terjadi di sini? Mengapa polanya {1,2}tidak bekerja?

Herosław Miraszewski
sumber
4
Ekspansi Brace tidak dilakukan dalam tanda kutip tunggal atau ganda
Sergiy Kolodyazhnyy
3
@SergiyKolodyazhnyy Inti dari pertanyaannya adalah bahwa ?itu juga dikutip, dan akan diperluas setelah $(...)menggantinya, tetapi ekspansi brace tidak.
Michael Homer
1
@muru Tidak, itu bukan masalah yang sama. Di sini urutan ekspansi tidak penting, yang penting ekspansi mana yang terjadi dalam konteks mana. Saya tidak akan terkejut jika pertanyaan ini merupakan duplikat, tetapi saya tidak dapat menemukannya.
Gilles 'SANGAT berhenti menjadi jahat'
1
@mosvy Ksh dan bash melakukan ekspansi dalam urutan yang sama, tetapi ksh menahan ekspansi dalam kasus di mana bash tidak melakukannya sama sekali. Zsh-with-globsubst melakukan ekspansi yang sama dengan bash, tetapi dalam urutan yang berbeda.
Gilles 'SANGAT berhenti menjadi jahat'
1
@ Gilles tidak, mereka tidak. Seperti yang didokumentasikan dan mudah ditunjukkan, ksh (dan zsh) akan melakukan ekspansi brace tepat sebelum globbing. zsh-dengan-globsubst tidak akan melakukan ekspansi penjepit sama sekali pada hasil $-expansions: zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'.
Mosvy

Jawaban:

17

Ini kombinasi dari dua hal. Pertama, ekspansi brace bukan pola yang cocok dengan nama file: ini adalah pengganti tekstual murni - lihat Apa perbedaan antara `a [bc] d` (kurung) dan` a {b, c} d` (kurung kurawal)? . Kedua, ketika Anda menggunakan hasil substitusi perintah di luar tanda kutip ganda ( ls $(…)), yang terjadi hanya pencocokan pola (dan pemisahan kata: operator "split + glob"), bukan penguraian ulang yang lengkap.

Dengan ls $(echo 'test?.txt'), perintah echo 'test?.txt'menampilkan string test?.txt(dengan baris baru akhir). Substitusi perintah menghasilkan string test?.txt(tanpa baris baru final, karena subtitusi perintah membuntuti baris baru). Substitusi yang tidak dikutip ini mengalami pemisahan kata, menghasilkan daftar yang terdiri dari string tunggal test?.txtkarena tidak ada karakter spasi (lebih tepatnya, tidak ada karakter $IFSdi dalamnya) di dalamnya. Setiap elemen dari daftar satu elemen ini kemudian mengalami ekspansi wildcard bersyarat, dan karena ada karakter wildcard ?dalam string, ekspansi wildcard tersebut benar-benar terjadi. Karena pola test?.txtcocok dengan setidaknya satu nama file, elemen daftar test?.txtdiganti dengan daftar nama file yang cocok dengan pola, menghasilkan daftar dua elemen yang mengandungtest1.txtdan test2.txt. Akhirnya lsdisebut dengan dua argumen test1dan test2.

Dengan ls $(echo 'test{1,2}'), perintah echo 'test{1,2}'menampilkan string test{1,2}(dengan baris baru akhir). Substitusi perintah menghasilkan string test{1,2}. Substitusi yang tidak dikutip ini mengalami pemisahan kata, menghasilkan daftar yang terdiri dari string tunggal test{1,2}. Setiap elemen dari daftar satu elemen ini kemudian mengalami ekspansi wildcard bersyarat, yang tidak melakukan apa-apa (elemen dibiarkan apa adanya) karena tidak ada karakter wildcard dalam string. Demikian lsdisebut dengan argumen tunggal test{1,2}.

Sebagai perbandingan, inilah yang terjadi ls $(echo test{1,2}). Perintah echo test{1,2}menampilkan string test1 test2(dengan baris terakhir akhir). Substitusi perintah menghasilkan string test1 test2(tanpa baris akhir akhir). Substitusi yang tidak dikutip ini mengalami pemisahan kata, menghasilkan dua string test1dan test2. Kemudian, karena tidak satu pun dari string berisi karakter wildcard, mereka dibiarkan sendiri, sehingga lsdisebut dengan dua argumen test1dan test2.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
3
Perhatikan bahwa pdksh dan ksh93 melakukan ekspansi brace dengan ekspansi (sebelum globbing; tidak dengan noglob, tetapi dalam kasus ksh93, masih ketika braceexpand dimatikan!)
Stéphane Chazelas
Anda sepertinya lupa .txtpada penjelasan kedua.
val berkata Reinstate Monica
10

Urutan ekspansi adalah: ekspansi brace; ekspansi tilde, ekspansi parameter dan variabel, ekspansi aritmatika, dan penggantian perintah (dilakukan dengan cara kiri-ke-kanan); pemisahan kata; dan ekspansi nama file.

Ekspansi brace tidak akan terjadi setelah penggantian perintah. Anda dapat menggunakan eval untuk memaksa putaran ekspansi lainnya:

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

Hasilnya adalah:

1lala 2lala
dedowsdi
sumber
6

Masalah itu sangat spesifik bash, dan itu karena mereka memutuskan bashuntuk memisahkan ekspansi brace dari ekspansi nama file (globbing), dan untuk melakukannya terlebih dahulu, sebelum semua ekspansi lainnya.

Dari halaman bashmanual:

Urutan ekspansi adalah: ekspansi brace; ekspansi tilde, ekspansi parameter dan variabel, ekspansi aritmatika, dan penggantian perintah (dilakukan dengan cara kiri-ke-kanan); pemisahan kata; dan perluasan pathname.

Dalam contoh Anda, bashhanya akan melihat kawat gigi Anda setelah melakukan penggantian perintah (yang $(echo ...)), ketika sudah terlambat.

Ini berbeda dari semua shell lainnya, yang melakukan ekspansi brace tepat sebelum (dan beberapa bahkan sebagai bagian dari) ekspansi pathname (globbing). Itu termasuk tetapi tidak terbatas pada cshtempat brace-ekspansi pertama kali ditemukan.

$ 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

Contoh terakhir adalah di sama csh, zsh, ksh93, mkshatau fish.

Juga, perhatikan bahwa penjepit ekspansi sebagai bagian dari globbing juga tersedia melalui glob(3)fungsi pustaka (setidaknya di Linux dan semua BSD), dan dalam implementasi independen lainnya (misalnya dalam perl:) perl -le 'print join " ", <test{1,2}.txt>'.

Mengapa hal itu dilakukan secara berbeda bashmungkin memiliki cerita di baliknya, tetapi FWIW saya tidak dapat menemukan penjelasan logis, dan saya menemukan semua rasionalisasi post-hoc tidak meyakinkan.

mosvy
sumber
3
Catatan yang perldulu digunakan untuk cshmemperluas gumpalan, jadi tidak mengherankan bahwa ia masih mengenali operator globbing yang sama dengancsh
Stéphane Chazelas
1

Silakan coba :::

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

Dengan BackSlash. Ini berfungsi sekarang. Hapus juga seperti kata poster sebelumnya, kutipan. Dot bukan untuk pola yang cocok, tetapi harus diambil secara harfiah sebagai Periode di sini.

mkzia
sumber
(1) Pertanyaannya bertanya, “Apa yang terjadi di sini? Mengapa [polanya] {1,2}berperilaku seperti itu? Pertanyaannya tidak bertanya, "Bagaimana saya bisa mendapatkan perintah menggunakan {1,2}untuk berperilaku cara kerja perintah ??" Anda menjawab pertanyaan yang salah. (2) Backslash tidak ada hubungannya dengan itu. Perintah Anda berfungsi sebagaimana mestinya karena Anda telah menghapus tanda kutip yang ada di perintah dalam pertanyaan.
G-Man Mengatakan 'Reinstate Monica'
0

Ini berfungsi jika Anda menghapus tanda kutip

$ ls $(echo test{1,2})
test1  test2
darxmurf
sumber
9
Ekspansi sekarang terjadi sebelum substitusi perintah, yang menurut saya bukan pertanyaan yang ditanyakan (bandingkan dengan apa yang terjadi ?).
Michael Homer