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

31

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

Agora, ainda espero encontrá-lo durante a execução find . -name '*'- na verdade, nunca esperaria que um nome de arquivo falhasse neste teste. No entanto, com LANG=en_US.utf8, o arquivo não aparece 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 prever esse resultado?

Informações: Arch Linux 3.14.37-1-lts, find (GNU findutils) 4.4.2

Michael
fonte
1
você já pensou convmvem converter nomes de arquivos para utf-8?
ctrl-alt-Delor
@richard: De fato, costumo confiar em [[ $file =~ '^.*$' ]]não usar recodeo nome do arquivo, mas agora analisarei convmvse necessário. Obrigado.
Michaël

Respostas:

25

Essa é uma pegadinha muito legal. De uma rápida olhada no código fonte para a descoberta GNU, eu diria que isso se resume a como fnmatchse comporta em seqüências de bytes inválidas ( pred_name_commonin pred.c):

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

Esse código testa o valor de retorno de fnmatchigualdade com 0, mas não verifica se há erros; isso resulta em qualquer erro relatado como "não corresponde".

Foi sugerido, há muitos anos, alterar o comportamento dessa função libc para sempre retornar verdadeiro no *padrão, mesmo em nomes de arquivos quebrados, mas pelo que posso dizer, a ideia deve ter sido rejeitada (consulte o tópico a partir de https : //sourceware.org/ml/libc-hacker/2002-11/msg00071.html ):

Quando o fnmatch detecta um caractere multibyte inválido, ele volta a corresponder a um byte, para que "*" tenha a chance de corresponder a uma string.

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

Como mencionado por Stéphane Chazelas em um comentário, e também no mesmo segmento de 2002, isso é inconsistente com a expansão glob realizada por shells, que não engasgam com caracteres inválidos. Talvez ainda mais intrigante seja o fato de que a reversão do teste corresponderá apenas aos arquivos que têm nomes quebrados (crie arquivos no bash com touch $'D\351marrer' $'Touch\303\251' $'\346\227\245\346\234\254\350\252\236'):

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

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

Portanto, para responder sua pergunta, você poderia ter previsto isso conhecendo o comportamento do seu fnmatchneste caso e sabendo como findlida com o valor de retorno dessa função; você provavelmente não poderia ter descoberto apenas lendo a documentação.

dhag
fonte
Meu palpite para o porquê de não haver solução *é que seria inconsistente D*staller.
ctrl-alt-Delor
7
@richard, a ideia seria que D*staller coincidisse $'D\351sinstaller'tão bem quanto na glob de todas as conchas que testei. Dado que o comportamento do GNU fnmatch não é consistente com o do shell GNU, eu diria que é um bug.
Stéphane Chazelas
1
Ótima resposta, dhag; muito apreciado. Você se importaria de apontar as especificações padrão às quais o fnmatch está em conformidade? Eu posso encontrar a especificação POSIX regexp usual especificando que .deve corresponder apenas a caracteres válidos na codificação - daí a minha expectativa de que .*não corresponda a cadeias inválidas - mas não consigo encontrar uma especificação correspondente para a estrela brilhante.
Michaël
1
A especificação mais próxima que posso encontrar on-line está nesta página do OpenGroup . Ele afirma que a correspondência deve se basear no padrão de bits usado para codificar o caractere, não na representação gráfica do caractere. e <asterisk> é um padrão que deve corresponder a qualquer sequência, incluindo a sequência nula. Isso pode ser interpretado como a sugestão de @ StéphaneChazelas. 13 anos depois, pode ser hora de executar ping a montante novamente :-)
Michaël
@ Michaël, também não consegui encontrar nada melhor. Talvez, como um ponto de comparação, a localização do GNU no Mac OS se comporte de maneira consistente com o globbing do shell (ou seja, -name '*'corresponde a todos os arquivos, incluindo nomes quebrados), então presumivelmente a versão do BSD fnmatch, que não reivindica o desempenho POSIX.2, ao contrário da versão GNU, tem uma interpretação diferente e sem dúvida mais saudável do que deve ser feito em caracteres inválidos.
dhag
13

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

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

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

Com D\351sinstaller, fnmatchreturn -1, indicou que não conseguiu corresponder. Um caractere válido como ሒaaserá correspondido.

No seu caso, com UTF-8código de idioma, \351é um caractere inválido, causando falha na correspondência de padrões.

cuonglm
fonte
3
No mínimo, +1 para o uso de ltrace. Eu sabia strace, mas ltraceé novo para mim. Encantador!
Michaël