find (1): bagaimana wildcard bintang diimplementasikan agar gagal pada beberapa nama file?

31

Dalam sistem file di mana nama file berada di UTF-8, saya punya file dengan nama yang salah; itu ditampilkan sebagai D�sinstaller:, nama aktual menurut zsh D$'\351'sinstaller:, Latin1 untuk Désinstaller, itu sendiri barbarisme Perancis untuk "uninstall." Zsh tidak akan cocok dengan itu [[ $file =~ '^.*$' ]]tetapi akan mencocokkannya dengan globbing *- ini adalah perilaku yang saya harapkan.

Sekarang saya masih berharap untuk menemukannya ketika menjalankan find . -name '*'- sebenarnya, saya tidak akan pernah berharap nama file gagal tes ini. Namun, dengan LANG=en_US.utf8, file tersebut tidak muncul, dan saya harus set LANG=C(atau en_US, atau '') untuk itu untuk bekerja.

Pertanyaan: Apa implementasi di belakang, dan bagaimana saya bisa memprediksi hasil itu?

Informasi: Arch Linux 3.14.37-1-lts, find (GNU findutils) 4.4.2

Michaël
sumber
1
sudahkah Anda mempertimbangkan convmvuntuk mengonversi nama file ke utf-8?
ctrl-alt-delor
@ Richard: Sebenarnya, saya gunakan untuk mengandalkan [[ $file =~ '^.*$' ]]gagal untuk menggunakan recodenama file, tapi sekarang saya akan melihat ke dalam convmvjika perlu. Terima kasih.
Michaël

Jawaban:

25

Itu tangkapan yang sangat bagus. Dari sekilas melihat kode sumber untuk menemukan GNU, saya akan mengatakan ini bermuara pada bagaimana fnmatchberperilaku pada urutan byte yang tidak valid ( pred_name_commondalam pred.c):

b = fnmatch (str, base, flags) == 0;
(...)
return b;

Kode ini menguji nilai pengembalian fnmatchuntuk kesetaraan dengan 0, tetapi tidak memeriksa kesalahan; ini menghasilkan kesalahan yang dilaporkan sebagai "tidak cocok".

Telah disarankan, bertahun-tahun yang lalu, untuk mengubah perilaku fungsi libc ini untuk selalu mengembalikan true pada *pola, bahkan pada nama file yang rusak, tetapi dari apa yang saya tahu ide itu pasti telah ditolak (lihat utas mulai di https : //sourceware.org/ml/libc-hacker/2002-11/msg00071.html ):

Ketika fnmatch mendeteksi karakter multibyte yang tidak valid, ia harus kembali ke pencocokan byte tunggal, sehingga "*" memiliki peluang untuk mencocokkan string tersebut.

Dan mengapa ini lebih baik atau lebih benar? Apakah ada praktik yang ada?

Seperti yang disebutkan oleh Stéphane Chazelas dalam komentar, dan juga di utas tahun 2002 yang sama, ini tidak konsisten dengan ekspansi glob yang dilakukan oleh shell, yang tidak tersumbat pada karakter yang tidak valid. Mungkin yang lebih membingungkan adalah kenyataan bahwa membalikkan tes hanya akan cocok dengan file yang memiliki nama yang rusak (buat file dalam bash with touch $'D\351marrer' $'Touch\303\251' $'\346\227\245\346\234\254\350\252\236'):

$ find -name '*'
.
./Touché
./日本語

$ find -not -name '*'
./D?marrer

Jadi, untuk menjawab pertanyaan Anda, Anda bisa memperkirakan ini dengan mengetahui perilaku Anda fnmatchdalam hal ini, dan mengetahui bagaimana findmenangani nilai pengembalian fungsi ini; Anda mungkin tidak bisa mengetahuinya hanya dengan membaca dokumentasi.

Dhag
sumber
Tebakan saya mengapa tidak ada perbaikan untuk *itu maka itu akan menjadi tidak konsisten dengan D*staller.
ctrl-alt-delor
7
@ Richard, idenya adalah yang D*stallerakan cocok $'D\351sinstaller'juga seperti itu di gumpalan semua kerang yang telah saya uji. Mengingat bahwa perilaku fnmatch GNU tidak konsisten dengan perilaku GNU shell, saya akan mengatakan itu bug.
Stéphane Chazelas
1
Jawaban mendalam yang bagus, dhag; sangat dihargai. Maukah Anda menunjukkan spesifikasi standar yang sesuai dengan fnmatch? Saya dapat menemukan spesifikasi regexp POSIX biasa yang .hanya cocok dengan karakter yang valid dalam pengkodean — maka harapan saya yang .*tidak cocok dengan string yang tidak valid — tetapi saya tidak dapat menemukan spesifikasi yang cocok untuk bintang globbing.
Michaël
1
Spesifikasi terdekat yang dapat saya temukan online ada di halaman OpenGroup ini . Ini menyatakan Pencocokan harus didasarkan pada pola bit yang digunakan untuk pengkodean karakter, bukan pada representasi grafis dari karakter. dan <asterisk> adalah pola yang cocok dengan string apa pun, termasuk string nol. Ini bisa diartikan sebagai saran @ StéphaneChazelas. 13 tahun kemudian, mungkin sudah waktunya untuk melakukan ping ke hulu lagi :-)
Michaël
@ Michael, aku juga tidak bisa menemukan yang lebih baik. Mungkin, sebagai titik perbandingan, GNU menemukan di Mac OS berperilaku konsisten dengan globbing shell (yaitu, -name '*'cocok dengan semua file, termasuk nama yang rusak), jadi mungkin versi BSD fnmatch, yang tidak mengklaim POSIX.2 cnoformance, tidak seperti versi GNU, memiliki interpretasi yang berbeda, dan bisa dibilang lebih waras, tentang apa yang harus dilakukan pada karakter yang tidak valid.
Dhag
13

temukan -name opsi menggunakan notasi pencocokan pola shell untuk melakukan pencocokan nama file. *adalah pola yang cocok dengan banyak karakter , harus cocok dengan untaian karakter nol atau lebih.

findmenggunakan fnmatch untuk memeriksa pencocokan pola, sehingga Anda dapat menggunakan ltrace untuk memeriksa hasilnya:

$ touch $'\U1212'aa
$ touch D$'\351'sinstaller
$ LC_ALL=en_US.utf8 ltrace -e fnmatch find -name '*'          
find->fnmatch("foo", "foo", 0)                   = 0
find->fnmatch("Foo", "foo", 0)                   = 1
find->fnmatch("Foo", "foo", 16)                  = 0
find->fnmatch("*", ".", 0)                       = 0
.
find->fnmatch("*", "D\351sinstaller", 0)         = -1
find->fnmatch("*", "\341\210\222aa", 0)          = 0
./ሒaa
+++ exited (status 0) +++

Dengan D\351sinstaller, fnmatchkembali -1, menunjukkan bahwa itu tidak cocok. Karakter seperti yang valid ሒaaakan dicocokkan.

Dalam kasus Anda, dengan UTF-8lokal, \351adalah karakter yang tidak valid, menyebabkan pencocokan pola gagal.

cuonglm
sumber
3
Paling tidak, +1 untuk penggunaan ltrace. Saya memang tahu strace, tetapi ltracebaru bagi saya. Menyenangkan!
Michaël