find (1): jak zaimplementowano symbol wieloznaczny gwiazdy, aby zawiódł w niektórych nazwach plików?

31

W systemie plików, w którym nazwy plików znajdują się w UTF-8, mam plik o błędnej nazwie; jest wyświetlany jako :, D�sinstallerrzeczywista nazwa według zsh D$'\351'sinstaller:, Latin1 dla Désinstaller, sam francuski barbarzyństwo dla „odinstaluj”. Zsh nie [[ $file =~ '^.*$' ]]pasowałby do tego, ale pasowałby do niego globbingiem *- takiego zachowania się oczekuję.

Teraz nadal oczekuję, że znajdę go podczas działania find . -name '*'- w rzeczywistości nigdy nie spodziewałbym się, że nazwa pliku nie przejdzie tego testu. Jednak z LANG=en_US.utf8, plik nie pojawia się i muszę ustawić LANG=C(lub en_US, lub ''), aby działał.

Pytanie: Jakie jest wdrożenie i jak mogłem przewidzieć ten wynik?

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

Michał
źródło
1
czy rozważałeś convmvkonwersję nazw plików na utf-8?
ctrl-alt-delor
@richard: W rzeczywistości polegam na [[ $file =~ '^.*$' ]]tym, że nie używam recodenazwy pliku, ale teraz zajrzę w convmvrazie potrzeby. Dzięki.
Michaël,

Odpowiedzi:

25

To naprawdę niezły haczyk. Po krótkim spojrzeniu na kod źródłowy GNU find, powiedziałbym, że sprowadza się to do tego, jak fnmatchzachowuje się w nieprawidłowych sekwencjach bajtów ( pred_name_commonin pred.c):

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

Ten kod testuje zwracaną wartość fnmatchrówności z 0, ale nie sprawdza błędów; powoduje to zgłaszanie wszelkich błędów jako „nie pasuje”.

Wiele lat temu zasugerowano zmianę zachowania tej funkcji libc, aby zawsze zwracała wartość true we *wzorcu, nawet w przypadku zepsutych nazw plików, ale z tego, co mogę powiedzieć, pomysł musiał zostać odrzucony (patrz wątek rozpoczynający się na https : //sourceware.org/ml/libc-hacker/2002-11/msg00071.html ):

Kiedy fnmatch wykryje niepoprawny znak wielobajtowy, powinien powrócić do dopasowania jednobajtowego, aby „*” miał szansę dopasować taki ciąg.

I dlaczego to jest lepsze czy bardziej poprawne? Czy istnieje praktyka?

Jak wspomniał Stéphane Chazelas w komentarzu, a także w tym samym wątku z 2002 r., Jest to niezgodne z rozszerzeniem globalnym wykonywanym przez powłoki, które nie dławią się nieprawidłowymi postaciami. Być może jeszcze bardziej zagadkowe jest to, że cofnięcie testu będzie pasowało tylko do plików, które mają niepoprawne nazwy (twórz pliki w skrócie touch $'D\351marrer' $'Touch\303\251' $'\346\227\245\346\234\254\350\252\236'):

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

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

Tak więc, aby odpowiedzieć na twoje pytanie, mógłbyś to przewidzieć, znając swoje zachowanie fnmatchw tym przypadku i wiedząc, jak findobsługuje wartość zwracaną przez tę funkcję; prawdopodobnie nie mogłeś się tego dowiedzieć, czytając dokumentację.

dhag
źródło
Domyślam się, dlaczego nie ma poprawki *, że byłoby to niespójne D*staller.
ctrl-alt-delor
7
@richard, pomysł byłby że D*stallerbędzie pasować $'D\351sinstaller'jak dobrze jak to ma miejsce w glob wszystkich pocisków, które testowałem. Biorąc pod uwagę, że zachowanie GNU fnmatch nie jest zgodne z zachowaniem powłoki GNU, powiedziałbym, że to błąd.
Stéphane Chazelas,
1
Wielka, niezgłębiona odpowiedź, dhag; bardzo mile widziane. Czy mógłbyś wskazać standardową specyfikację zgodną z programem fnmatch? Mogę znaleźć zwykłe wyrażenie regularne POSIX określające, że .powinny one pasować tylko do prawidłowych znaków w kodowaniu - stąd moje oczekiwania, które .*nie pasują do niepoprawnych łańcuchów - ale nie mogę znaleźć pasującej specyfikacji dla globującej gwiazdy.
Michaël,
1
Najbliższa specyfikacja, jaką mogę znaleźć online, znajduje się na tej stronie OpenGroup . Stwierdzono, że dopasowanie powinno opierać się na wzorze bitowym użytym do kodowania znaku, a nie na graficznej reprezentacji znaku. a <asterisk> to wzorzec, który będzie pasował do dowolnego łańcucha, w tym łańcucha zerowego. Można to interpretować jako sugestię @ StéphaneChazelas. 13 lat później może być czas na pingowanie w górę :-)
Michaël,
@ Michaël, nie mogłem znaleźć nic lepszego. Być może, dla porównania, GNU find w Mac OS zachowuje się w sposób spójny z globowaniem powłoki (tj. Pasuje do -name '*'wszystkich plików, w tym zepsutymi nazwami), więc prawdopodobnie wersja BSD fnmatch, która nie deklaruje zgodności z POSIX.2, w przeciwieństwie do wersji GNU, ma inną i prawdopodobnie rozsądniejszą interpretację tego, co należy zrobić w przypadku nieprawidłowych znaków.
wtorek
13

-nameOpcja find używa notacji dopasowującej wzór powłoki do wykonania pasującej nazwy pliku. *to wzór pasujący do wielu znaków , powinien pasować ciąg zerowy lub więcej znaków.

findużywa fnmatch do sprawdzania dopasowania wzorca, więc możesz użyć ltrace do sprawdzenia wyniku:

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

Z D\351sinstaller, fnmatchreturn -1, wskazuje, że nie udało się dopasować. Prawidłowy znak jak ሒaazostanie dopasowany.

W twoim przypadku, z UTF-8ustawieniami regionalnymi, \351jest niepoprawnym znakiem, co powoduje niepowodzenie dopasowania wzorca.

Cuonglm
źródło
3
Przynajmniej +1 za użycie ltrace. Wiedziałem o tym strace, ale ltracejest dla mnie nowy. Śliczny!
Michaël,