find (1): come viene implementato il carattere jolly stella in modo che non riesca su alcuni nomi di file?


31

In un file system in cui i nomi dei file sono in UTF-8, ho un file con un nome errato; viene visualizzato come :, D�sinstallernome effettivo secondo zsh D$'\351'sinstaller:, Latin1 per Désinstaller, esso stesso una barbarie francese per "disinstallazione". Zsh non lo abbinerebbe, ma lo abbinerebbe [[ $file =~ '^.*$' ]]a un sussulto: *questo è il comportamento che mi aspetto.

Ora mi aspetto ancora di trovarlo durante l'esecuzione find . -name '*', infatti, non mi aspetterei mai che un nome di file fallisca questo test. Tuttavia, con LANG=en_US.utf8, il file non viene visualizzato e devo impostare LANG=C(o en_US, o '') affinché funzioni.

Domanda: qual è l'implementazione dietro e come avrei potuto prevedere tale risultato?

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


1
hai pensato convmvdi convertire i nomi dei file in utf-8?
ctrl-alt-delor,

@richard: In effetti, mi affido al fatto che non riesco [[ $file =~ '^.*$' ]]a utilizzare recodeil nome del file, ma ora esaminerò convmvse necessario. Grazie.
Michaël,

Risposte:


25

È davvero una bella cattura. Da una rapida occhiata al codice sorgente di GNU find, direi che questo si riduce a come fnmatchsi comporta su sequenze di byte non valide ( pred_name_commonin pred.c):

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

Questo codice verifica il valore restituito di fnmatchper l'uguaglianza con 0, ma non verifica la presenza di errori; ciò comporta che qualsiasi errore venga segnalato come "non corrisponde".

È stato suggerito, molti anni fa, di modificare il comportamento di questa funzione libc per tornare sempre vero sul *modello, anche su nomi di file non funzionanti, ma da quello che posso dire l'idea deve essere stata respinta (vedi il thread che inizia da https : //sourceware.org/ml/libc-hacker/2002-11/msg00071.html ):

Quando fnmatch rileva un carattere multibyte non valido, dovrebbe ricadere nella corrispondenza a byte singolo, in modo che "*" abbia la possibilità di abbinare tale stringa.

E perché è meglio o più corretto? Esiste una pratica esistente?

Come menzionato da Stéphane Chazelas in un commento, e anche nello stesso thread del 2002, ciò non è coerente con l'espansione glob eseguita da shell, che non soffocano su caratteri non validi. Forse ancora più sconcertante è il fatto che l'inversione del test corrisponderà solo a quei file che hanno nomi spezzati (creare file in bash con touch $'D\351marrer' $'Touch\303\251' $'\346\227\245\346\234\254\350\252\236'):

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

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

Quindi, per rispondere alla tua domanda, avresti potuto prevederlo conoscendo il tuo comportamento fnmatchin questo caso e sapendo come findgestisce il valore di ritorno di questa funzione; probabilmente non avresti potuto scoprirlo solo leggendo la documentazione.


La mia ipotesi sul motivo per cui non esiste una soluzione *è che sarebbe incompatibile con D*staller.
ctrl-alt-delor,

7
@richard, l'idea sarebbe quella di D*stalleradattarsi allo $'D\351sinstaller'stesso modo nel glob di tutte le shell che ho testato. Dato che il comportamento GNU fnmatch non è coerente con quello della shell GNU, direi che è un bug.
Stéphane Chazelas,

1
Grande risposta approfondita, dhag; molto apprezzato. Ti dispiacerebbe sottolineare le specifiche standard a cui fnmatch è conforme? Riesco a trovare le solite specifiche regexp POSIX che specificano che .devono corrispondere solo a caratteri validi nella codifica, quindi le mie aspettative .*non corrispondono a stringhe non valide, ma non riesco a trovare una specifica corrispondente per la stella globbing.
Michaël,

1
La specifica più vicina che posso trovare online è su questa pagina OpenGroup . Indica che la corrispondenza deve essere basata sul modello di bit utilizzato per codificare il carattere, non sulla rappresentazione grafica del carattere. e <asterisco> è un modello che deve corrispondere a qualsiasi stringa, compresa la stringa nulla. Questo può essere probabilmente interpretato come il suggerimento di @ StéphaneChazelas. 13 anni dopo, potrebbe essere il momento di eseguire nuovamente il ping a monte :-)
Michaël

@ Michaël, non sono riuscito a trovare niente di meglio. Forse, come punto di confronto, GNU find su Mac OS si comporta in modo coerente con il globbing della shell (cioè, -name '*'corrisponde a tutti i file, inclusi i nomi non funzionanti), quindi presumibilmente la versione di BSD di fnmatch, che non rivendica il cnoformance di POSIX.2, a differenza della versione GNU, ha un'interpretazione diversa e probabilmente più sana di ciò che dovrebbe essere fatto su caratteri non validi.
dhag,

13

L' -name opzione find utilizza la notazione di corrispondenza del modello shell per eseguire il nome file corrispondente. *è un modello che corrisponde a più caratteri , deve corrispondere a una stringa di zero o più caratteri.

findusa fnmatch per verificare la corrispondenza dei pattern, quindi puoi usare ltrace per controllare il risultato:

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

Con D\351sinstaller, fnmatchreturn -1, indica che non è stato possibile abbinare. ሒaaVerrà abbinato un personaggio valido come .

Nel tuo caso, con le impostazioni UTF-8internazionali, \351è un carattere non valido, che causa la mancata corrispondenza del modello.


3
Almeno, +1 per l'uso di ltrace. Lo sapevo strace, ma ltraceè nuovo per me. Bello!
Michaël,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.