2009-02-06 10 views
39

Desidero leggere l'input UTF-8 in Perl, indipendentemente dal fatto che provenga dallo standard input o da un file, utilizzando l'operatore diamond: while(<>){...}.Come faccio a leggere UTF-8 con operatore diamantato (<>)?

Quindi il mio script deve essere richiamabile in questi due modi, come al solito, dando la stessa uscita:

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

Ma le uscite diverse! Solo la seconda chiamata (usando cat) sembra funzionare come progettata, leggendo correttamente UTF-8. Ecco lo script:

#!/usr/bin/perl -w 

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

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

Come posso farlo leggere UTF-8 correttamente in entrambi i casi? Vorrei continuare a utilizzare l'operatore diamond <> per la lettura, se possibile.

EDIT:

ho capito probabilmente avrei dovuto descrivere i diversi uscite. Il mio file di input contiene questa sequenza: a\xCA\xA7b. Il metodo con cat uscite correttamente:

a 
\xCA\xA7 
b 

Ma l'altro metodo mi dà questo:

a 
\xC3\x8A 
\xC2\xA7 
b 

risposta

54

tenta di utilizzare la direttiva open, invece:

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

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

Hai bisogno di fare questo perché l'operatore <> è magico. Come sai, leggerà da STDIN o dai file in @ARGV. La lettura da STDIN non causa problemi poiché STDIN è già aperto, quindi binmode funziona bene su di esso. Il problema è quando si legge dai file in @ARGV, quando lo script inizia e chiama binmode i file non sono aperti. Ciò fa sì che STDIN sia impostato su UTF-8, ma questo canale IO non viene utilizzato quando @ARGV ha file. In questo caso l'operatore < apre un nuovo handle di file per ogni file in @ARGV. Ogni handle di file viene ripristinato e perde il suo attributo UTF-8. Usando il pragma aperto costringi ogni nuovo STDIN ad essere in UTF-8.

16

Lo script funziona se si fa questo:

#!/usr/bin/perl -w 

binmode STDOUT, ':utf8'; 

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

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

Il filehandle magia che <> legge da si chiama *ARGV, ed è aperto quando si chiama readline.

Ma in realtà, sono un fan di utilizzare esplicitamente Encode::decode e Encode::encode quando appropriato.

+0

È necessario avere il binmode nel frattempo perché ARGV viene ripristinato per più file? –

+1

sperimentalmente, sì :) – jrockway

+2

Ho guardato questo e ho pensato, "Non funzionerà! Stai impostando' binmode' dopo che la prima riga è già stata letta da '<>' ". Comunque, l'ho provato, e * funziona *. Altamente magico. – mavit

9

È possibile accendere UTF8 di default con la bandiera -C:

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

L'interruttore -CSD accende UTF8 incondizionatamente; se si utilizza semplicemente -C si attiva UTF8 solo se le variabili di ambiente pertinenti (LC_ALL, LC_TYPE e LANG) indicano così. Vedi perlrun per i dettagli.

Questo non è raccomandato se non si richiama direttamente perl (in particolare, potrebbe non funzionare in modo affidabile se si passano le opzioni a perl dalla riga shebang).Vedi le altre risposte in quel caso.

+0

C'è un problema con l'opzione -C da perl 5.10 http://www.fi.muni.cz/~kas/blog/index.cgi/computers/too-late-for-cs-howto.html –

+0

Off topic: Uso '#!/usr/bin/perl' non è raccomandato linea di shebang, vedi perlrun per i dettagli. Se non usi l'approccio perlrun, usa #!/Usr/bin/env perl che è più portabile di #!/Usr/bin/perl –

+0

Grazie, ho messo in chiaro che dovresti usarlo solo quando invochi direttamente perl. –

4

Se si effettua una chiamata a binmode all'interno del ciclo while, si passerà alla modalità utf8 DOPO che la prima riga viene letta. Probabilmente non è ciò che si vuole fare.

Qualcosa di simile al seguente potrebbe funzionare meglio:

#!/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 chiamata a eof() con parentesi è magico, come controlla per la fine del file sul pseudo-filehandle usato da <>. Se necessario, aprirà l'handle successivo che deve essere letto, che in genere ha l'effetto di rendere * ARGV valido, ma senza leggerne nulla. Questo ci permette di codificare il primo file letto, prima che qualcosa venga letto da esso.

Successivamente, viene utilizzato eof (senza paren); questo controlla l'ultimo handle letto per la fine del file. Sarà vero dopo aver elaborato l'ultima riga di ogni file dalla riga di comando (o quando stdin raggiunge la sua fine).

Ovviamente, se abbiamo appena elaborato l'ultima riga di un file, chiamando eof() (con parens) si apre il file successivo (se ce n'è uno), rende * ARGV valido (se possibile) e test per la fine del file su quel file successivo. Se quel file successivo è presente e non è alla fine del file, allora possiamo tranquillamente usare binmode su ARGV.