tìm (1): làm thế nào để ký tự đại diện sao được thực hiện cho nó không thành công trên một số tên tệp?


31

Trong một hệ thống tệp có tên tệp trong UTF-8, tôi có một tệp có tên bị lỗi; nó được hiển thị dưới dạng : D�sinstaller, tên thật theo zsh : D$'\351'sinstaller, Latin1 Désinstaller, bản thân nó là một sự man rợ của Pháp đối với "gỡ cài đặt". Zsh sẽ không phù hợp với nó [[ $file =~ '^.*$' ]]nhưng sẽ phù hợp với nó với một tinh thần toàn cầu *là hành vi mà tôi mong đợi.

Bây giờ tôi vẫn mong đợi tìm thấy nó khi chạy find . -name '*'một số vấn đề thực tế, tôi sẽ không bao giờ mong đợi một tên tệp sẽ thất bại trong bài kiểm tra này. Tuy nhiên, với LANG=en_US.utf8, tệp không hiển thị và tôi phải đặt LANG=C(hoặc en_US, hoặc '') để nó hoạt động.

Câu hỏi: Việc triển khai đằng sau là gì và làm thế nào tôi có thể dự đoán kết quả đó?

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


1
Bạn đã cân nhắc convmvchuyển đổi tên tệp thành utf-8 chưa?
ctrl-alt-delor

@richard: Trên thực tế, tôi thường dựa vào [[ $file =~ '^.*$' ]]việc không sử dụng recodetên tệp, nhưng bây giờ tôi sẽ xem xét convmvnếu cần. Cảm ơn.
Michaël

Câu trả lời:


25

Đó là một đánh bắt thực sự tốt đẹp. Từ một cái nhìn nhanh về mã nguồn cho GNU find, tôi sẽ nói điều này hiểu rõ về cách fnmatchhành xử trên các chuỗi byte không hợp lệ ( pred_name_commonin pred.c):

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

Mã này kiểm tra giá trị trả về của fnmatchđẳng thức bằng 0, nhưng không kiểm tra lỗi; điều này dẫn đến bất kỳ lỗi nào được báo cáo là "không khớp".

Nhiều năm trước, đã có đề xuất thay đổi hành vi của hàm libc này để luôn trả về đúng trên *mẫu, ngay cả trên tên tệp bị hỏng, nhưng từ những gì tôi có thể nói ý tưởng phải bị từ chối (xem chủ đề bắt đầu từ https : //sourceware.org/ml/libc-hacker/2002-11/msg00071.html ):

Khi fnmatch phát hiện một ký tự đa nhân không hợp lệ, nó sẽ quay trở lại khớp một byte, do đó "*" có cơ hội khớp với một chuỗi như vậy.

Và tại sao điều này tốt hơn hay đúng hơn? Có thực hành hiện có?

Như Stéphane Chazelas đã đề cập trong một bình luận, và cũng trong cùng một chủ đề năm 2002, điều này không phù hợp với việc mở rộng toàn cầu được thực hiện bởi shell, không gây nghẹt thở cho các ký tự không hợp lệ. Có lẽ khó hiểu hơn nữa là việc đảo ngược bài kiểm tra sẽ chỉ khớp với những tệp có tên bị hỏng (tạo tệp trong bash với touch $'D\351marrer' $'Touch\303\251' $'\346\227\245\346\234\254\350\252\236'):

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

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

Vì vậy, để trả lời câu hỏi của bạn, bạn có thể dự đoán điều này bằng cách biết hành vi của bạn fnmatchtrong trường hợp này và biết cách findxử lý giá trị trả về của hàm này; bạn có thể không thể tìm ra chỉ bằng cách đọc tài liệu.


Tôi đoán tại sao không có sửa chữa *là sau đó nó sẽ không phù hợp với D*staller.
ctrl-alt-delor

7
@richard, ý tưởng sẽ D*stallerphù hợp với $'D\351sinstaller'nó giống như trên toàn cầu của tất cả các vỏ tôi đã thử nghiệm. Cho rằng hành vi fnmatch của GNU không phù hợp với lớp vỏ GNU, tôi muốn nói rằng đó là một lỗi.
Stéphane Chazelas

1
Câu trả lời sâu sắc tuyệt vời, dhag; Nhiều đánh giá cao. Bạn có phiền khi chỉ ra thông số kỹ thuật tiêu chuẩn mà fnmatch tuân thủ? Tôi có thể tìm thấy thông số regex POSIX thông thường chỉ định .chỉ khớp với các ký tự hợp lệ trong mã hóa do đó kỳ vọng của tôi .*không khớp với chuỗi không hợp lệ, nhưng tôi không thể tìm thấy thông số kỹ thuật phù hợp cho ngôi sao toàn cầu.
Michaël

1
Thông số kỹ thuật gần nhất tôi có thể tìm thấy trực tuyến là trên trang Opengroup này . Nó tuyên bố Kết hợp sẽ dựa trên mẫu bit được sử dụng để mã hóa ký tự, không dựa trên biểu diễn đồ họa của ký tự. <asterisk> là một mẫu phù hợp với bất kỳ chuỗi nào, bao gồm cả chuỗi null. Điều này có thể được hiểu là gợi ý của @ StéphaneChazelas. 13 năm sau, có lẽ đã đến lúc ping ngược dòng một lần nữa :-)
Michaël

@ Michaël, tôi cũng không thể tìm thấy bất cứ điều gì tốt hơn. Có lẽ, như một điểm so sánh, GNU tìm thấy trên Mac OS hoạt động theo cách phù hợp với tính toàn cầu của shell (nghĩa là -name '*'khớp với tất cả các tệp, bao gồm các tên bị hỏng), do đó, có lẽ là phiên bản của BSD fnmatch, không yêu cầu POSIX.2 cnoformance, Không giống như phiên bản GNU, có một cách giải thích khác, và được cho là sạch hơn, việc giải thích những gì nên được thực hiện trên các ký tự không hợp lệ.
dhag

13

-nametùy chọn find sử dụng ký hiệu khớp mẫu shell để thực hiện khớp tên tệp. *là một mẫu khớp với nhiều ký tự , sẽ khớp với một chuỗi có 0 hoặc nhiều ký tự.

findsử dụng fnmatch để kiểm tra khớp mẫu, vì vậy bạn có thể sử dụng ltrace để kiểm tra kết quả:

$ 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) +++

Với D\351sinstaller, fnmatchtrở lại -1, chỉ ra rằng nó không phù hợp. Một nhân vật hợp lệ như ሒaasẽ được khớp.

Trong trường hợp của bạn, với UTF-8miền địa phương, \351là một ký tự không hợp lệ, gây ra lỗi khớp mẫu.


3
Ít nhất, 1 cho việc sử dụng ltrace. Tôi đã biết về strace, nhưng ltracelà mới đối với tôi. Đáng yêu!
Michaël
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.