Diberikan nama file ini:
$ ls -1
file
file name
otherfile
bash
itu sendiri tidak masalah dengan embedded whitespace:
$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?
Namun, kadang-kadang saya mungkin tidak ingin bekerja dengan setiap file, atau bahkan secara ketat $PWD
, yang mana find
masuk. Yang juga menangani spasi putih secara nominal:
$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name
Saya mencoba untuk menyusun versi whispace-aman ini scriptlet yang akan mengambil output dari find
dan menyampaikannya ke select
:
$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file
Namun, ini meledak dengan spasi putih di nama file:
$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file 3) name 5) ./directory/file
2) ./file 4) ./directory/file 6) name
Biasanya, aku akan menyelesaikan masalah ini dengan bermain-main IFS
. Namun:
$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
Apa solusinya?
bash
text-processing
whitespace
select
DopeGhoti
sumber
sumber
find
karena kemampuannya untuk mencocokkan nama file tertentu, Anda dapat menggunakanselect file in **/file*
(setelah pengaturanshopt -s globstar
) dalambash
4 atau lebih baru.Jawaban:
Jika Anda hanya perlu menangani spasi dan tab (bukan baris baru yang disematkan) maka Anda dapat menggunakan
mapfile
(atau sinonimnyareadarray
) untuk membaca ke dalam array misalnya diberikankemudian
Jika Anda lakukan perlu menangani baris baru, dan Anda
bash
versi menyediakan null-delimitedmapfile
1 , maka Anda dapat memodifikasi bahwa untukIFS= mapfile -t -d '' files < <(find . -type f -print0)
. Jika tidak, kumpulkan array yang setara darifind
output yang dibatasi-nol menggunakanread
loop:1 yang
-d
pilihan ditambahkan kemapfile
dalambash
versi 4.4 iircsumber
mapfile
ini adalah hal baru bagi saya juga. Pujian.while IFS= read
Versi bekerja kembali di bash v3 (yang penting bagi kita menggunakan MacOS).find -print0
varian; menggerutu karena meletakkannya setelah versi yang diketahui salah, dan menggambarkannya hanya untuk digunakan jika ada yang tahu bahwa mereka perlu menangani baris baru. Jika seseorang hanya menangani hal-hal yang tidak terduga di tempat-tempat yang diharapkan, ia tidak akan pernah menangani hal yang tidak terduga sama sekali.Jawaban ini memiliki solusi untuk semua jenis file. Dengan baris atau spasi baru.
Ada solusi untuk bash baru-baru ini serta bash kuno dan bahkan kerang posix lama.
Pohon yang tercantum di bawah dalam jawaban ini [1] digunakan untuk pengujian.
Pilih
Mudah
select
untuk bekerja dengan array:Atau dengan parameter posisi:
Jadi, satu-satunya masalah sebenarnya adalah mendapatkan "daftar file" (dibatasi dengan benar) di dalam array atau di dalam Parameter Posisi. Teruslah membaca.
pesta
Saya tidak melihat masalah yang Anda laporkan dengan bash. Bash dapat mencari di dalam direktori yang diberikan:
Atau, jika Anda menyukai loop:
Perhatikan bahwa sintaks di atas akan bekerja dengan benar dengan shell (wajar) (setidaknya csh).
Satu-satunya batasan yang dimiliki sintaks di atas adalah turun ke direktori lain.
Tapi bash bisa melakukan itu:
Untuk memilih hanya beberapa file (seperti yang berakhir pada file) cukup ganti *:
kuat
Ketika Anda menempatkan "ruang- aman " di judul, saya akan menganggap bahwa apa yang Anda maksudkan adalah " kuat ".
Cara paling sederhana untuk menjadi kuat tentang spasi (atau baris baru) adalah menolak pemrosesan input yang memiliki spasi (atau baris baru). Cara yang sangat sederhana untuk melakukan ini di shell adalah keluar dengan kesalahan jika ada nama file yang mengembang dengan spasi. Ada beberapa cara untuk melakukan ini, tetapi yang paling ringkas (dan posix) (tetapi terbatas pada satu isi direktori, termasuk nama direktori dan menghindari dot-file) adalah:
Jika solusi yang digunakan kuat di salah satu item tersebut, hapus tes.
Dalam bash, sub-direktori dapat diuji sekaligus dengan ** yang dijelaskan di atas.
Ada beberapa cara untuk memasukkan file dot, solusi Posix adalah:
Temukan
Jika find harus digunakan karena alasan tertentu, ganti pembatas dengan NUL (0x00).
bash 4.4+
bash 2.05+
POSIXLY
Untuk membuat solusi POSIX yang valid di mana find tidak memiliki pembatas NUL dan tidak ada
-d
(atau-a
) untuk membaca kita perlu pendekatan yang berbeda secara keseluruhan.Kita perlu menggunakan kompleks
-exec
dari find dengan panggilan ke shell:Atau, jika yang diperlukan adalah pilih (pilih adalah bagian dari bash, bukan sh):
[1] Pohon ini (\ 012 adalah baris baru):
Dapat dibangun dengan dua perintah ini:
sumber
Anda tidak bisa mengatur variabel di depan konstruksi perulangan, tetapi Anda bisa mengaturnya di depan kondisi. Inilah segmen dari halaman manual:
(Pengulangan bukan perintah sederhana .)
Berikut ini adalah konstruk yang umum digunakan yang menunjukkan skenario kegagalan dan kesuksesan:
Sayangnya saya tidak bisa melihat cara untuk menanamkan perubahan
IFS
ke dalamselect
konstruk sementara itu mempengaruhi proses yang terkait$(...)
. Namun, tidak ada yang mencegahIFS
diatur di luar loop:dan konstruk inilah yang bisa saya lihat berfungsi dengan
select
:Saat menulis kode defensif, saya akan merekomendasikan agar klausa dijalankan dalam subkulit, atau
IFS
danSHELLOPTS
disimpan dan dipulihkan di sekitar blok:sumber
IFS=$'\n'
aman tidak berdasar. Nama file sangat bisa mengandung literal baris baru.[0-9a-f]{24}
. TB cadangan data yang digunakan untuk mendukung tagihan pelanggan hilang.select
oleh desainnya adalah untuk solusi scripted , sehingga harus selalu dirancang untuk menangani kasus tepi.select
dari shell tempat Anda mengetik perintah untuk dijalankan, tetapi hanya di skrip, di mana Anda menjawab prompt yang disediakan oleh skrip itu , dan di mana skrip itu berada mengeksekusi logika yang telah ditentukan (dibangun tanpa sepengetahuan nama file yang dioperasikan) berdasarkan input itu.Saya mungkin berada di luar yurisdiksi saya di sini, tetapi mungkin Anda dapat mulai dengan sesuatu seperti ini, setidaknya tidak ada masalah dengan spasi putih:
Untuk menghindari anggapan yang keliru, seperti disebutkan dalam komentar, perlu diketahui bahwa kode di atas setara dengan:
sumber
read -d
adalah solusi cerdas; Terima kasih untuk ini.read -d $'\000'
adalah persis identik denganread -d ''
, tetapi untuk menyesatkan orang-orang tentang kemampuan bash (menyiratkan, tidak benar, bahwa itu dapat mewakili NULs literal dalam string). Jalankans1=$'foo\000bar'; s2='foo'
, dan kemudian coba temukan cara untuk membedakan antara kedua nilai tersebut. (Versi masa depan dapat dinormalkan dengan perilaku substitusi perintah dengan membuat nilai yang disimpan setara denganfoobar
, tapi itu tidak terjadi hari ini).