find (1): как реализован подстановочный знак «звезда» для сбоя некоторых имен файлов?

31

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

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

Вопрос: Что такое реализация и как я мог предсказать этот результат?

Информация: Arch Linux 3.14.37-1-lts, find (GNU findutils) 4.4.2

Майкл
источник
1
Вы думали convmvконвертировать имена файлов в UTF-8?
Ctrl-Alt-Delor
@ Richard: На самом деле, я использую, чтобы [[ $file =~ '^.*$' ]]не указывать recodeимя файла, но сейчас я посмотрю, convmvесли это будет необходимо. Спасибо.
Микаэль

Ответы:

25

Это действительно хороший улов. Из быстрого взгляда на исходный код для поиска GNU я бы сказал, что это сводится к тому, как fnmatchведет себя недопустимые последовательности байтов ( pred_name_commonin 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 года, это не согласуется с расширением глобуса, выполняемым оболочками, которые не блокируют недопустимые символы. Возможно, еще более загадочным является тот факт, что при реверсе теста будут сопоставляться только те файлы, которые имеют сломанные имена (создайте файлы в bash с помощью 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обрабатывает возвращаемое значение этой функции; Вы, вероятно, не могли бы узнать только прочитав документацию.

dhag
источник
Мое предположение, почему нет исправления, *состоит в том, что тогда это будет несовместимо с D*staller.
Ctrl-Alt-Delor
7
@ Richard, идея была бы в том, чтобы D*stallerэто соответствовало $'D\351sinstaller'так же, как это происходит в глобусе всех оболочек, которые я тестировал. Учитывая, что поведение GNU fnmatch не соответствует поведению оболочки GNU, я бы сказал, что это ошибка.
Стефан Шазелас
1
Великий глубокий ответ, дхаг; очень признателен. Не могли бы вы указать стандартную спецификацию, которой соответствует fnmatch? Я могу найти обычную спецификацию регулярного выражения POSIX, определяющую, что она .должна соответствовать только действительным символам в кодировке - отсюда мое ожидание, которое .*не соответствует недопустимым строкам - но я не могу найти подходящую спецификацию для всплывающей звезды.
Микаэль
1
Ближайшая спецификация, которую я могу найти онлайн, находится на этой странице OpenGroup . В нем говорится, что сопоставление должно основываться на битовой комбинации, используемой для кодирования символа, а не на графическом представлении символа. и <звездочка> является шаблоном, который должен соответствовать любой строке, включая нулевую строку. Это можно интерпретировать как предложение @ StéphaneChazelas. 13 лет спустя, возможно, пришло время снова пинговать вверх по течению :-)
Микаэль
@ Микаэль, я тоже не мог найти ничего лучше. Возможно, для сравнения GNU find в 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-8локалью, \351это недопустимый символ, приводящий к сбою сопоставления с шаблоном.

cuonglm
источник
3
По крайней мере, +1 за использование ltrace. Я знал об этом strace, но ltraceдля меня это ново. Прекрасный!
Микаэль