perl - ¿En qué codificación readdir devuelve un nombre de archivo?
ubuntu encoding (1)
La pregunta reformulada (como la interpreto) es:
¿Por qué
readdir
no devuelve el nombre de archivo recién creado? (Aquí, representado por elfilename
la variable que se establece enBärlauch
).
(Nota: filename
es una variable constante de Perl, por eso es que falta el $
sigil en el frente).
Fondo:
Primera nota: debido al use utf8
instrucción use utf8
al comienzo de su programa, el filename
se actualizará a una cadena Unicode en el momento de la compilación, ya que contiene caracteres que no son ASCII. De la documentación del pragma utf8 :
Habilitar el pragma utf8 tiene el siguiente efecto: los bytes en el texto fuente que no están en el juego de caracteres ASCII serán tratados como parte de una secuencia literal UTF-8. Esto incluye la mayoría de los literales, como nombres de identificadores, constantes de cadenas y patrones de expresiones regulares constantes.
y también, de acuerdo con la sección perluniintro "Modelo Unicode de Perl" :
El principio general es que Perl intenta mantener sus datos en bytes de ocho bits el mayor tiempo posible, pero tan pronto como no se pueda evitar Unicodeness, los datos se actualizan de forma transparente a Unicode.
...
Internamente, Perl actualmente usa cualquier cosa que el conjunto de caracteres nativo de ocho bits de la plataforma (por ejemplo, Latin-1), por defecto sea UTF-8, para codificar cadenas Unicode.
El carácter no ASCII en filename
de filename
es la letra ä
. Si utiliza la codificación ASCII extendida ISO 8859-1 (Latin-1), se codifica como el valor de byte 0xE4
, consulte esta tabla en ascii-code.com
. Sin embargo, si eliminó el carácter ä
desde el filename
del filename
, solo contendría caracteres ASCII y, por lo tanto, no se actualizaría internamente a Unicode, incluso si usó utf8
pragma.
Así que el filename
ahora es una cadena Unicode con el indicador UTF-8
interno configurado (ver utf8 pragma para más información sobre el indicador UTF-8
). Tenga en cuenta que la letra ä
está codificada en UTF-8 como los dos bytes 0xC3 0xA4
.
Escribiendo el archivo:
Al escribir el archivo, ¿qué ocurre con el nombre del archivo? Si filename
es una cadena Unicode, se codificará como UTF-8. Sin embargo, tenga en cuenta que no es necesario codificar primero el filename
( encode_utf8( filename )
). Consulte Creación de nombres de archivos con caracteres Unicode para obtener más información. Entonces el nombre del archivo se escribe en el disco como bytes codificados en UTF-8.
Leyendo el nombre del archivo de vuelta:
Al intentar leer el nombre de archivo desde el disco, readdir
no devuelve cadenas Unicode (cadenas con el conjunto de banderas UTF-8), incluso si el nombre de archivo contiene bytes codificados en UTF-8. Devuelve cadenas binarias o de bytes, consulte perlunitut para una discusión de cadenas de bytes frente a cadenas de caracteres (Unicode).
¿Por qué readdir
no devuelve cadenas Unicode? Primero, de acuerdo con la sección perlunicode "Cuando Unicode no ocurre" :
Todavía hay muchos lugares donde Unicode (en alguna codificación u otra) se podría dar como argumentos o recibir como resultados, o ambos en Perl, pero no es así. (...)
Las siguientes son tales interfaces. Para todas estas interfaces, Perl actualmente (a partir de v5.16.0) simplemente asume cadenas de bytes como argumentos y resultados. (...)
Una razón por la que Perl no intenta resolver el rol de Unicode en estas situaciones es que las respuestas dependen en gran medida del sistema operativo y de los sistemas de archivos. Por ejemplo, si los nombres de archivo pueden estar en Unicode y en exactamente qué tipo de codificación, no es exactamente un concepto portátil. (...)
- chdir, chmod, chown, chroot, exec, link, lstat, mkdir, rename, rmdir, - stat, symlink, truncate, unlink, utime, -X
- % ENV
- glob (también conocido como <*>)
- abierto, opendir, sysopen
- qx (también conocido como el operador backtick), sistema
- readdir, readlink
Entonces readdir
devuelve cadenas de bytes, ya que, en general, es imposible conocer la codificación de un nombre de archivo a priori. Para obtener información general sobre por qué esto es imposible, consulte, por ejemplo:
- nombre de archivo en Wikipedia, subsección "Interoperabilidad de codificación",
- Comprender la codificación del nombre de archivo Unix en unix.stackexchange.com
Comparación de cadenas:
Ahora, finalmente intenta comparar el nombre $filename_read
lectura $filename_read
con el nombre $filename_read
de la variable:
print "found/n" if $filename_read eq filename;
En este caso, la única diferencia entre $filename_read
y filename
es que $filename_read
no tiene configurado el indicador UTF-8 (no es lo que Perl reconoce internamente como una "cadena Unicode" ).
Lo interesante ahora es que el resultado del operador eq
dependerá de si los bytes en $filename_read
son puros ASCII o no. De acuerdo con la documentación del módulo Encode :
Antes de la introducción del soporte Unicode en Perl, el operador
eq
acaba de comparar las cadenas representadas por dos escalares. Comenzando con Perl 5.8,eq
compara dos cadenas con la consideración simultánea de la bandera UTF8....
Cuando decodifica, el indicador UTF8 resultante está activado, a menos que pueda representar datos de manera no ambigua.
Entonces en su caso, eq
considerará la bandera UTF-8
ya que $file_name_read
no contiene ASCII puro, y como resultado considerará que las dos cadenas no son iguales. Si $filename_read
y filename
idénticos y solo contenían bytes ASCII puros (y el filename
todavía tenía establecido el indicador UTF-8, $filename_read
no tenía establecido el indicador UTF-8), entonces eq
consideraría las dos cadenas como iguales. Se la discusión en la documentación para Codificar más información sobre el fondo de este comportamiento.
Conclusión:
Por lo tanto, si está relativamente seguro de que todos sus nombres de archivo están codificados en UTF-8, podría resolver el problema en su pregunta decodificando la cadena de bytes devuelta desde readdir
en una cadena Unicode (forzando que se establezca el indicador UTF-8):
$filename_read = Encode::decode_utf8( $filename_read );
Más detalles
Nota: dado que Unicode permite múltiples representaciones de los mismos caracteres, existen dos formas de ä
(LETRA A MINÚSCULA LATINA CON COMBINACIÓN DE DIAESIS) en Bärlauch
. Por ejemplo,
- U + 00E4 es la forma NFC (Composición canónica de la forma de normalización),
- U + 0061.0308 es la forma NFD (Descomposición canónica de forma de normalización).
En mi plataforma (Linux), los nombres de archivo codificados en UTF-8 se almacenan usando el formato NFC, pero en Mac OS usan el formato NFD. Ver Encode::UTF8Mac
para más información. Esto significa que si trabaja en una máquina Linux y, por ejemplo, clona un repositorio Git creado por un usuario de Mac, puede obtener fácilmente nombres codificados NFD en su máquina Linux. Por lo tanto, al sistema de archivos de Linux no le importa en qué codificación se encuentra un nombre de archivo; solo lo considera una secuencia de bytes. Por lo tanto, podría escribir fácilmente un script que creara un nombre de archivo codificado ISO-Latin-1, aunque mi "en_US.UTF-8"
regional es "en_US.UTF-8"
. La configuración regional actual es solo una guía para las aplicaciones, pero si la aplicación ignora la configuración regional, no es nada lo que les impide hacerlo.
Por lo tanto, si no está seguro de si los nombres de archivo devueltos por readdir
usan NFC o NFD, siempre debe descomponerse después de haberlos decodificado:
use Unicode::Normalize;
print "found/n" if NFD( $filename_read ) eq NFD( filename );
Consulte también la sección del Libro de cocina de Perl Unicode "Descomponer y recomponer siempre".
Finalmente, para comprender más acerca de cómo funciona la configuración regional junto con Unicode en Perl, puede echar un vistazo a:
- perllocale , sección "Unicode y UTF-8", y
- Encode :: Locale .
Aquí hay un script de Perl que esperaba imprimir found
cuando se ejecutó:
#!/usr/bin/perl
use warnings;
use strict;
use utf8;
use Encode;
use constant filename => ''Bärlauch'';
open (my $out, ''>'', filename) or die;
close $out;
opendir(my $dir, ''.'') or die;
while (my $filename_read = readdir($dir)) {
# $filename_read = encode(''utf8'', $filename_read);
print "found/n" if $filename_read eq filename;
}
La secuencia de comandos primero crea un archivo con el nombre del nombre de filename
constante. (Después de ejecutar el script, puedo verificar la existencia del archivo con ls
y el archivo no se crea con caracteres "divertidos").
A continuación, el script itera sobre los archivos en el directorio de trabajo actual e imprime found
si hay un archivo cuyo nombre es igual al archivo que acaba de crearse. Esto obviamente debería ser el caso.
Sin embargo, no (Ubuntu, bash, LANG=en_US.UTF8
)
Si cambio la constante a Barlauch
, funciona como se esperaba y se found
impresiones.
Sin comentar $filename_read = encode(''utf8'', $filename_read);
no cambia el comportamiento.
¿Hay una explicación para esto y qué debo hacer para reconocer un nombre de archivo con Umlaute en él?