find (1): como o curinga em estrela é implementado para que ele falhe em alguns nomes de arquivos?

30

Em um sistema de arquivos no qual nomes de arquivos estão em UTF-8, eu tenho um arquivo com um nome incorreto; ele é exibido como: D�sinstaller , nome real de acordo com zsh: D$'1'sinstaller , Latin1 para Désinstaller , ele próprio um barbarismo francês para "uninstall". O Zsh não combinaria com [[ $file =~ '^.*$' ]] , mas combinaria com um * globbing - esse é o comportamento que eu espero.

Agora, ainda espero encontrá-lo ao executar find . -name '*' . Na verdade, nunca esperaria que um nome de arquivo falhasse nesse teste. No entanto, com LANG=en_US.utf8 , o arquivo não é exibido, e eu tenho que definir LANG=C (ou en_US ou '' ) para que ele funcione.

Pergunta: Qual é a implementação por trás e como eu poderia ter previsto esse resultado?

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

    
por Michaël 09.04.2015 / 18:52

2 respostas

25

Isso é uma pegadinha realmente boa. De uma rápida olhada no código-fonte do GNU find, eu diria que isso se resume a como fnmatch se comporta em seqüências de bytes inválidos ( pred_name_common in pred.c ):

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

Este código testa o valor de retorno de fnmatch para igualdade com 0, mas não verifica erros; Isso resulta em qualquer erro sendo relatado como "não corresponde".

Foi sugerido, há muitos anos atrás, alterar o comportamento dessa função libc para sempre retornar true no padrão * , mesmo em nomes de arquivos quebrados, mas pelo que eu posso dizer a ideia deve ter sido rejeitada ( veja o tópico que começa no link ):

When fnmatch detects an invalid multibyte character it should fall back to single byte matching, so that "*" has a chance to match such a string.

     

E por que isso é melhor ou mais correto? Existe prática?

Como mencionado por Stéphane Chazelas em um comentário, e também no mesmo tópico de 2002, isso é inconsistente com a expansão glob realizada por shells, que não se engasgam com caracteres inválidos. Talvez ainda mais intrigante seja o fato de que a reversão do teste corresponderá apenas aos arquivos que tiverem nomes quebrados (crie arquivos no bash com touch $'D1marrer' $'Touch31' $'675644026' ):

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

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

Assim, para responder à sua pergunta, você poderia ter previsto isso sabendo o comportamento de seu fnmatch neste caso, e sabendo como find manipula o valor de retorno dessa função; você provavelmente não poderia ter descoberto apenas lendo a documentação.

    
por 09.04.2015 / 19:57
13

encontrar -name opção usa shell notação de correspondência de padrões para executar o nome de arquivo correspondente. * é um padrão que combina vários caracteres , deve corresponder a uma sequência de zero ou mais caracteres.

find usa fnmatch para verificar a correspondência de padrões, para que você possa usar ltrace para verificar o resultado:

$ touch $'\U1212'aa
$ touch D$'1'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("*", "D1sinstaller", 0)         = -1
find->fnmatch("*", "102aa", 0)          = 0
./ሒaa
+++ exited (status 0) +++

Com D1sinstaller , fnmatch return -1 , indicou que não foi possível corresponder. Um caractere válido como ሒaa será correspondido.

No seu caso, com UTF-8 locale, 1 é um caractere inválido, causando falha na correspondência de padrões.

    
por 09.04.2015 / 19:55