Schreiben und lesen von Dateien mit Unicode Zeichen auf Windows
Nele Kosog
- perl
0 Nele Kosog0 Beat0 Nele Kosog0 Beat
0 Nele Kosog
Hallo!
Ich versuche vergeblich eine von mir angelegte Datei, deren Name Unicode Zeichen enthält, wieder zu lesen.
Mein System: Windows XP, Perl Camelbox 5.10.0.
Angelegt wird die Datei so:
use utf8;
use strict;
use warnings;
use diagnostics;
use Encode qw( encode decode );
use Symbol qw( gensym );
use Win32API::File qw(
CreateFileW
OsFHandleOpen
CREATE_ALWAYS
OPEN_EXISTING
GENERIC_WRITE
GENERIC_READ
);
my @lines = ();
my $file_name = "йбный.txt"; # Beispielhafter Dateiname zum Testen
my $content = "пйрнобый"; # Beispielhafter Inhalt zum Testen
my $encoding = "UTF-8"; # Encoding UTF-8
### WRITE
my $win32_file_handle = CreateFileW(
encode( 'UTF-16LE', $file_name ), # Encode file name
GENERIC_WRITE, # For writing
0, # Not shared
[], # Security attributes
CREATE_ALWAYS, # Create and replace
0, # Special flags
[], # Permission template
)
or die("CreateFile: $^E\n");
OsFHandleOpen(
my $fh = gensym(), # Create global reference
$win32_file_handle, # File handle
'w', # Open for writing
)
or die("OsFHandleOpen: $^E\n");
print $fh encode( # Write to file
$encoding, # Encoding is UTF-8
$content, # File content in UTF-8
);
[...]
Bis hierhin funktioniert es.
Recherche:
Ich versuche die Datei danach wie folgt zu lesen:
[...]
### READ
my $win32_file_handle = CreateFileW(
encode( 'UTF-16LE', $file_name ), # Encode file name
GENERIC_READ, # For reading
0, # Not shared
[], # Security attributes
OPEN_EXISTING, # Open existing
0, # Special flags
[], # Permission template
)
or die("CreateFileW: $^E\n");
OsFHandleOpen(
my $fh = gensym(), # Create global reference
$win32_file_handle, # File handle
'r' # Open for reading
)
or die("OsFHandleOpen: $^E\n");
while (<$fh>) { # Read lines
push @lines,
decode( $encoding, $_ ); # Decode UTF-8
}
Das Programm bricht beim Lese-Aufruf von CreateFileW mit der Fehlermeldung "The system cannot find the file specified [...]".
Das lese ich als "System kann die angegebene Datei nicht finden" - aufgrund des Dateinamens? Ich bin mittlerweile betriebsblind - ich sehe den Fehler einfach nicht. Doppeltes Encoding?
Alle Tipps sind willkommen!
Vielen Dank für eure Hilfe,
Nele
Es funktioniert dann doch etwas anders.
Zum Beispiel so:
use utf8;
binmode STDOUT, ":encoding(UTF-8)";
binmode STDIN, ":encoding(UTF-8)";
use strict;
use warnings;
use diagnostics;
use Encode qw( encode decode );
use Symbol qw( gensym );
use Win32API::File qw(
CreateFileW
OsFHandleOpen
CREATE_ALWAYS
OPEN_EXISTING
GENERIC_WRITE
GENERIC_READ
);
### Test Case
my $encoding = 'UTF-8'; # Encoding UTF-8
my $file_name = 'пробный/пробный.txt'; # File name for testing purposes
my $content = 'пйрнобый'; # File content for testing purposes
### Write file
write_file( file_name => $file_name, encoding => $encoding, content => $content, );
### Read file
my @lines = read_file( file_name => $file_name, encoding => $encoding, );
### Output
foreach (@lines) { print $_; }
###----------------------------------------------------------------------------
sub read_file {
###----------------------------------------------------------------------------
### USAGE: read_file(
### file_name => <file name>,
### encoding => <your encoding>,
### );
###----------------------------------------------------------------------------
#shift;
my %parameter = @_;
my @lines = ();
if ( defined $parameter{file_name} && $parameter{file_name} ne '' ) {
if ( defined $parameter{encoding} && $parameter{encoding} ne '' ) {
my $win32_file_handle = CreateFileW(
encode( 'UTF-16LE', $parameter{file_name} ),
GENERIC_READ, # For reading
0, # Not shared
[], # Security attributes
OPEN_EXISTING, # Open existing file
0, # Special flags
[], # Permission template
)
or die("CreateFileW: $^E\n");
OsFHandleOpen( my $rfh = gensym(), $win32_file_handle, 'r' )
or die("OsFHandleOpen: $^E\n");
while (<$rfh>) {
push @lines, decode( $encoding, $_ );
}
}
}
else {
die("Cannot write file. No file name defined.");
}
return @lines;
}
###----------------------------------------------------------------------------
sub write_file {
###----------------------------------------------------------------------------
### USAGE: write_file(
### file_name => <file name>,
### encoding => <your encoding>,
### content => <your content>,
### );
###----------------------------------------------------------------------------
#shift;
my %parameter = @_;
if ( defined $parameter{file_name} && $parameter{file_name} ne '' ) {
if ( defined $parameter{encoding} && $parameter{encoding} ne '' ) {
my $win32_file_handle = CreateFileW(
encode( 'UTF-16LE', $parameter{file_name} ),
GENERIC_WRITE, # For writing
0, # Not shared
[], # Security attributes
CREATE_ALWAYS, # Create and replace
0, # Special flags
[], # Permission template
)
or die("CreateFileW: $^E\n");
OsFHandleOpen( my $wfh = gensym(), $win32_file_handle, 'w' )
or die("OsFHandleOpen: $^E\n");
print $wfh encode( $parameter{encoding}, $parameter{content} );
}
}
else {
die("Cannot write file. No file name defined.");
}
}
Einschränkungen:
Verbesserungsvorschläge sind willkommen!
Viele Grüße,
Nele
sub read_file {
...
my %parameter = @_;
...
while (<$rfh>) { push @lines, decode( $encoding, $\_ ); }
Da ist ein Problem mit deiner Routine.
Du hast Sie so verfasst, als ob sie auf jedes Encoding anwendbar sein sollte, also auch auf Files, die eine BOM beinhalten.
Aus diesem Grund solltest du hier Files im Slurpmode einlesen
{ local $/=undef;
my $file = decode($encoding, <$rfh>);
}
Verbesserungsvorschläge sind willkommen!
Wüsste nicht wirklich viel zu sagen...
mfg Beat
Hallo Beat!
Da ist ein Problem mit deiner Routine.
Du hast Sie so verfasst, als ob sie auf jedes Encoding anwendbar sein sollte, also auch auf Files, die eine BOM beinhalten.
Aus diesem Grund solltest du hier Files im Slurpmode einlesen
{ local $/=undef;
my $file = decode($encoding, <$rfh>);
}
Mal davon abgesehen, dass deine Variante um ein Vielfaches schneller ist als meine zeilenorientierte - warum soll ich die Datei slurpen? Ach, weil ich sonst das BOM verliere und eine einzelne Zeile gar nicht interpretieren kann, richtig? Kannst du es kurz erklären?
Und bevor ich es vergesse: Die Funktion CreateFileW kann natürlich mit Devices umgehen. Ich habe den Pfad nicht korrekt angegeben: Mit 'C:\dir\file_name.txt'
klappt es!
Danke & viele Grüße,
Nele
Mal davon abgesehen, dass deine Variante um ein Vielfaches schneller ist als meine zeilenorientierte - warum soll ich die Datei slurpen? Ach, weil ich sonst das BOM verliere und eine einzelne Zeile gar nicht interpretieren kann, richtig? Kannst du es kurz erklären?
Du hast die Erklärung ja schon fast.
Wenn du von UTF-16 decodierst, dann wird eine BOM erwartet.
Jedoch die BOM existiert nur in der ersten Zeile. Als was werden die ersten Bytes jeder nächsten Zeile interpretiert? Ich habe es nicht getestet.
mfg Beat
Wenn du von UTF-16 decodierst, dann wird eine BOM erwartet.
Jedoch die BOM existiert nur in der ersten Zeile.
Stimmt - du hast recht. :-)
Danke & viele Grüße,
Nele
Und hier noch ein Beispiel für das Löschen einer Datei:
###----------------------------------------------------------------------------
sub delete_file {
###----------------------------------------------------------------------------
### USAGE: delete_file (
### file_name => <file name>,
### );
###----------------------------------------------------------------------------
#shift;
my %parameter = @_;
if ( defined $parameter{file_name} && $parameter{file_name} ne '' ) {
DeleteFileW( encode( 'UTF-16LE', $parameter{file_name} ) )
or die("CreateFileW: $^E\n");
}
else {
die("Cannot delete file. No file name defined.");
}
}
Viele Grüße,
Nele