find (1): ¿cómo se implementa el comodín estrella para que falle en algunos nombres de archivo?


31

En un sistema de archivos donde los nombres de archivo están en UTF-8, tengo un archivo con un nombre defectuoso; se muestra como:, D�sinstallernombre real de acuerdo con zsh:, D$'\351'sinstallerLatin1 para Désinstallersí mismo, una barbarie francesa para "desinstalar". Zsh no coincidiría con él, [[ $file =~ '^.*$' ]]pero lo haría con un problema: este *es el comportamiento que espero.

Ahora todavía espero encontrarlo cuando lo find . -name '*'ejecuto; de hecho, nunca esperaría que un nombre de archivo falle esta prueba. Sin embargo, con LANG=en_US.utf8, el archivo no aparece, y tengo que configurarlo LANG=C(o en_US, o '') para que funcione.

Pregunta: ¿Cuál es la implementación detrás y cómo podría haber predicho ese resultado?

Información: Arch Linux 3.14.37-1-lts, find (GNU findutils) 4.4.2


1
¿ha considerado convmvconvertir nombres de archivo a utf-8?
ctrl-alt-delor

@richard: De hecho, solía confiar en [[ $file =~ '^.*$' ]]no usar recodeel nombre del archivo, pero ahora lo investigaré convmvsi es necesario. Gracias.
Michaël

Respuestas:


25

Esa es una muy buena captura. De un vistazo rápido al código fuente de GNU find, diría que esto se reduce a cómo se fnmatchcomporta en secuencias de bytes no válidas ( pred_name_commonen pred.c):

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

Este código prueba el valor de retorno de fnmatchigualdad con 0, pero no verifica los errores; esto da como resultado que cualquier error sea reportado como "no coincide".

Se sugirió, hace muchos años, cambiar el comportamiento de esta función libc para que siempre devuelva verdadero en el *patrón, incluso en nombres de archivos rotos, pero por lo que puedo decir, la idea debe haber sido rechazada (vea el hilo que comienza en https : //sourceware.org/ml/libc-hacker/2002-11/msg00071.html ):

Cuando fnmatch detecta un carácter multibyte no válido, debe recurrir a la coincidencia de un solo byte, de modo que "*" tenga la posibilidad de hacer coincidir dicha cadena.

¿Y por qué es esto mejor o más correcto? ¿Existe alguna práctica existente?

Según lo mencionado por Stéphane Chazelas en un comentario, y también en el mismo hilo de 2002, esto es inconsistente con la expansión glob realizada por los proyectiles, que no ahogan los caracteres no válidos. Quizás aún más desconcertante es el hecho de que revertir la prueba solo coincidirá con aquellos archivos que tienen nombres rotos (cree archivos en 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

Entonces, para responder a su pregunta, podría haber predicho esto al conocer el comportamiento de su fnmatchen este caso y al saber cómo findmaneja el valor de retorno de esta función; probablemente no podría haberlo descubierto únicamente leyendo la documentación.


Mi conjetura de por qué no hay una solución *es que entonces sería incompatible con D*staller.
ctrl-alt-delor

77
@richard, la idea sería que D*stallercoincidiera $'D\351sinstaller'tan bien como en el conjunto de todos los proyectiles que he probado. Dado que el comportamiento de GNU fnmatch no es consistente con el del shell GNU, diría que es un error.
Stéphane Chazelas

1
Gran respuesta en profundidad, dhag; muy apreciado. ¿Te importaría señalar la especificación estándar a la que cumple fnmatch? Puedo encontrar la especificación de expresiones regulares POSIX que especifica que .solo debe coincidir con caracteres válidos en la codificación, de ahí mi expectativa de que .*no coincida con cadenas no válidas, pero no puedo encontrar una especificación coincidente para la estrella globbing.
Michaël

1
La especificación más cercana que puedo encontrar en línea está en esta página de OpenGroup . Establece que la coincidencia se basará en el patrón de bits utilizado para codificar el carácter, no en la representación gráfica del carácter. y <asterisk> es un patrón que debe coincidir con cualquier cadena, incluida la cadena nula. Podría decirse que esto puede interpretarse como la sugerencia de @ StéphaneChazelas. 13 años más tarde, puede ser hora de hacer ping de nuevo :-)
Michaël

@ Michaël, tampoco pude encontrar nada mejor. Quizás, como punto de comparación, el hallazgo de GNU en Mac OS se comporta de manera coherente con el bloqueo del shell (es decir, -name '*'coincide con todos los archivos, incluidos los nombres rotos incluidos), por lo que presumiblemente es la versión de BSD fnmatch, que no reclama POSIX.2 cnoformance, a diferencia de la versión de GNU, tiene una interpretación diferente y posiblemente más sensata de lo que se debe hacer con caracteres no válidos.
dhag

13

La -name opción de búsqueda utiliza la notación de coincidencia de patrón de shell para realizar la coincidencia del nombre de archivo. *es un patrón que coincide con varios caracteres , debe coincidir con una cadena de cero o más caracteres.

findusa fnmatch para verificar la coincidencia de patrones, por lo que puede usar ltrace para verificar el 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) +++

Con D\351sinstaller, fnmatchreturn -1, indicó que no pudo coincidir. Se ሒaaemparejará un carácter válido como .

En su caso, con la UTF-8configuración regional, \351es un carácter no válido, lo que hace que falle la coincidencia de patrones.


3
Como mínimo, +1 por el uso de ltrace. Lo sabía strace, pero ltracees nuevo para mí. ¡Encantador!
Michaël
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.