2009-02-06 22 views
39

Quiero leer la entrada UTF-8 en Perl, sin importar si proviene de la entrada estándar o de un archivo, usando el operador de diamantes: while(<>){...}.¿Cómo leo UTF-8 con operador de diamante (<>)?

Así que mi script debe ser exigible en estas dos formas, como de costumbre, dando el mismo resultado:

./script.pl utf8.txt 
cat utf8.txt | ./script.pl 

Pero las salidas variar! Solo la segunda llamada (usando cat) parece funcionar como está diseñada, leyendo UTF-8 correctamente. Aquí está el script:

#!/usr/bin/perl -w 

binmode STDIN, ':utf8'; 
binmode STDOUT, ':utf8'; 

while(<>){ 
    my @chars = split //, $_; 
    print "$_\n" foreach(@chars); 
} 

¿Cómo puedo hacer que lea UTF-8 correctamente en ambos casos? Me gustaría seguir usando el operador de diamante <> para leer, si es posible.

EDIT:

me di cuenta de que probablemente debería describir las diferentes salidas. Mi archivo de entrada contiene esta secuencia: a\xCA\xA7b. El método con cat emite correctamente:

a 
\xCA\xA7 
b 

Pero el otro método me da esto:

a 
\xC3\x8A 
\xC2\xA7 
b 

Respuesta

54

intenta utilizar el pragma abierta en su lugar:

use strict; 
use warnings; 
use open qw(:std :utf8); 

while(<>){ 
    my @chars = split //, $_; 
    print "$_" foreach(@chars); 
} 

que tiene que hacer esto porque el operador <> es mágico. Como sabe, leerá de STDIN o de los archivos en @ARGV. Leer de STDIN no causa ningún problema ya que STDIN ya está abierto, por lo que binmode funciona bien en él. El problema es cuando lee de los archivos en @ARGV, cuando se inicia su secuencia de comandos y llama a binmode, los archivos no están abiertos. Esto hace que STDIN se configure en UTF-8, pero este canal IO no se usa cuando @ARGV tiene archivos. En este caso, el operador <> abre un nuevo identificador de archivo para cada archivo en @ARGV. Cada identificador de archivo se restablece y pierde su atributo UTF-8. Al utilizar pragma abierto, fuerza cada nueva STDIN para que esté en UTF-8.

16

Su script funciona si usted hace esto:

#!/usr/bin/perl -w 

binmode STDOUT, ':utf8'; 

while(<>){ 
    binmode ARGV, ':utf8'; 

    my @chars = split //, $_; 
    print "$_\n" foreach(@chars); 
} 

El gestor de archivo mágica que <> lee de *ARGV se llama, y ​​es abrieron cuando se llama a readline.

Pero en realidad, soy un fan de utilizar explícitamente Encode::decode y Encode::encode cuando corresponda.

+0

¿Tiene que tener el binmode mientras tanto porque ARGV se restablece para varios archivos? –

+1

experimentalmente, sí :) – jrockway

+2

Miré esto y pensé, "¡Eso no funcionará! Estás configurando' binmode' después de que la primera línea ya se haya leído de '<>' ". Sin embargo, lo probé, y * funciona *. Altamente mágico. – mavit

9

Se puede cambiar en UTF8 por defecto con el -C bandera:

perl -CSD -ne 'print join("\n",split //);' utf8.txt 

El interruptor se enciende -CSD UTF8 sin condiciones; si usa simplemente -C, activará UTF8 solo si las variables de entorno relevantes (LC_ALL, LC_TYPE y LANG) lo indican. Ver perlrun para más detalles.

Esto no se recomienda si no se invoca directamente a perl (en particular, podría no funcionar de manera confiable si pasa opciones a Perl desde la línea de shebang).Ver las otras respuestas en ese caso.

+0

Existe un problema con -C cambiar desde perl 5.10 http://www.fi.muni.cz/~kas/blog/index.cgi/computers/too-late-for-cs-howto.html –

+0

Tema aparte: Uso de '#!/usr/bin/perl' no es una línea shebang recomendada, consulte perlrun para más detalles. Si no usa el enfoque perlrun use #!/Usr/bin/env perl, que es más portable que #!/Usr/bin/perl –

+0

Gracias, dejé en claro que solo debería usar esto cuando invoque perl directamente. –

4

Si pone una llamada al binmode dentro del ciclo while, cambiará el manejador al modo utf8 DESPUÉS de que se lea la primera línea. Probablemente no sea lo que quiere hacer.

algo como lo siguiente podría funcionar mejor:

#!/usr/bin/env perl -w 
binmode STDOUT, ':utf8'; 
eof() ? exit : binmode ARGV, ':utf8'; 
while(<>) { 
    my @chars = split //, $_; 
    print "$_\n" foreach(@chars); 
} continue { 
    binmode ARGV, ':utf8' if eof && !eof(); 
} 

La llamada a EOF() con parens es mágico, ya que comprueba el final del archivo en el pseudo-gestor de archivo utilizado por <>. Si es necesario, abrirá el siguiente identificador que necesita ser leído, que típicamente tiene el efecto de hacer que * ARGV sea válido, pero sin leer nada. Esto nos permite binmodear el primer archivo que se lee, antes de que se lea algo de él.

Más tarde, se usa eof (sin paréntesis); esto verifica el último identificador que se leyó para el final del archivo. Será cierto después de procesar la última línea de cada archivo desde la línea de comandos (o cuando stdin llegue a su final).

Obviamente, si acabamos de procesar la última línea de un archivo, al llamar a eof() (con parens) se abre el siguiente archivo (si hay uno), hace que * ARGV sea válido (si es posible) y prueba para el final del archivo en el siguiente archivo. Si el siguiente archivo está presente y no está al final del archivo, entonces podemos usar binmode de forma segura en ARGV.

Cuestiones relacionadas