find (1): як реалізовано підстановку зірки, щоб вона не змогла отримати деякі назви файлів?


31

У файловій системі, де назви файлів є в UTF-8, у мене є файл із несправним іменем; вона відображається як:, D�sinstallerфактична назва відповідно до zsh:, D$'\351'sinstallerLatin1 for Désinstaller, сама по собі французьке варварство для "видалення". Zsh не погодився б з цим, [[ $file =~ '^.*$' ]]але зіставлю його з глобусом *- це така поведінка, яку я очікую.

Тепер я все ще сподіваюся знайти його під час роботи find . -name '*'- фактично я ніколи не очікував, що ім'я файлу не вдасться провести цей тест. Однак, LANG=en_US.utf8файл не відображається, і я повинен встановити LANG=C(або en_US, або ''), щоб він працював.

Питання: Що стоїть за реалізацією, і як я міг передбачити цей результат?

Інформація: Arch Linux 3.14.37-1-lts, знайдіть (GNU findutils) 4.4.2


1
Ви думали convmvперетворити імена файлів у utf-8?
ctrl-alt-delor

@richard: Насправді я покладаюся на [[ $file =~ '^.*$' ]]те, що не використовую recodeім'я файлу, але зараз я розглядаю, convmvякщо це потрібно. Спасибі.
Michaël

Відповіді:


25

Це дійсно приємний улов. Швидкий погляд на вихідний код пошуку GNU, я б сказав, що це зводиться до того, як fnmatchповодиться недійсні послідовності байтів ( pred_name_commonв pred.c):

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

Цей код перевіряє повернене значення fnmatchрівності з 0, але не перевіряє на помилки; це призводить до того, що будь-які помилки повідомляються як "не відповідає".

Багато років тому було запропоновано змінити поведінку цієї функції libc, щоб завжди повертати справжнє в *шаблоні навіть на зламані імена файлів, але з того, що я можу сказати, ідею, можливо, було відхилено (див. Нитку, починаючи з https : //sourceware.org/ml/libc-hacker/2002-11/msg00071.html ):

Коли fnmatch виявить недійсний мультибайтовий символ, він повинен повернутися до однобайтового зіставлення, так що "*" має шанс зіставити такий рядок.

І чому це краще чи правильніше? Чи існує існуюча практика?

Як згадував Стефан Шазелас у коментарі, а також у тій же нитці 2002 року, це суперечить глобальному розширенню, здійсненому оболонками, які не задихаються від недійсних символів. Можливо, ще більш дивовижним є той факт, що скасування тесту відповідатиме лише тим файлам, які мають зламані імена (створюють файли в базі з touch $'D\351marrer' $'Touch\303\251' $'\346\227\245\346\234\254\350\252\236')

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

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

Отже, щоб відповісти на ваше запитання, ви могли це передбачити, знаючи поведінку вашої fnmatchу цьому випадку та знаючи, як findобробляє зворотні значення цієї функції; ви, мабуть, не могли цього дізнатися виключно, прочитавши документацію.


Думаю, чому немає виправлень, *це те, що тоді це було б невідповідно D*staller.
ctrl-alt-delor

7
@richard, ідея була б такою D*staller, $'D\351sinstaller'як і у всіх сферах, які я протестував. Враховуючи, що поведінка fnmatch GNU не відповідає поведінці оболонки GNU, я б сказав, що це помилка.
Стефан Шазелас

1
Прекрасна відповідь, дхаг; цінується. Ви б заперечили вказати на стандартну специфікацію, якій відповідає fnmatch? Я можу знайти звичайну специфікацію POSIX regexp із зазначенням, що .в кодуванні повинні відповідати лише дійсні символи - отже, моє очікування, що .*не відповідає недійсним рядкам, - але я не можу знайти специфікацію відповідності для зірки, що поширюється.
Michaël

1
Найближча специфікація, яку я можу знайти в Інтернеті, знаходиться на цій сторінці OpenGroup . У ньому зазначено, що відповідність має базуватися на бітовій схемі, що використовується для кодування символу, а не на графічному зображенні символу. і <asterisk> - це шаблон, який повинен відповідати будь-якій рядку, включаючи нульову рядок. Це, можливо, можна трактувати як пропозицію @ StéphaneChazelas. Через 13 років, можливо, прийде час знову пінг-по течії :-)
Michaël

@ Michaël, я не зміг знайти нічого кращого. Можливо, для порівняння пошук GNU в Mac OS поводиться таким чином, що відповідає глобальній обшивці оболонки (тобто -name '*'відповідає всім файлам, включеним зламаним іменам), тому, імовірно, BSD-версії fnmatch, яка не вимагає невідповідності POSIX.2, На відміну від версії GNU, має іншу і, можливо, безпечнішу інтерпретацію того, що слід робити на недійсних символах.
даг

13

-nameОпція find використовує позначення відповідності шаблону оболонки для виконання відповідного імені файлу. *є візерунком відповідає декільком символам , повинен відповідати рядку з нуля або більше символів.

findвикористовує fnmatch для перевірки відповідності шаблонів, тому ви можете використовувати ltrace для перевірки результату:

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

З D\351sinstaller, fnmatchповернення -1, вказало, що воно не вдалося збігтися. Дійсний символ на зразок ሒaaбуде збігатися.

У вашому випадку з UTF-8locale \351є недійсним символом, через що збіг шаблону не вдається.


3
Як мінімум, +1 для використання ltrace. Я про це знав strace, але ltraceдля мене це нове. Прекрасна!
Michaël
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.