Perl: Умлаут-проблема с именами файлов в windows - PullRequest
1 голос
/ 13 января 2020

Я написал программу на perl, которая манипулирует (создавать, удалять, открывать, закрывать, читать, записывать, копировать и т. Д. c.) Файлами и каталогами. Это очень хорошо работает при работе на Linux (Ubuntu), а также на macOS. Но он должен выполнять ту же работу и под windows, и там у меня проблемы с кодировкой имен файлов, которые содержат символы, отличные от ASCII (например, немецкие умлауты, но также и любые другие символы, не входящие в ASCII).

Поскольку моя оригинальная программа слишком велика, я создал более короткую программу для тестирования.
Это сокращенный эквивалент моей первой наивной версии моей perl программы (сам файл программы кодируется как UTF- 8):

#!/usr/bin/perl -w

use strict;
use warnings;

my $filename = 'FäöüßÄÖÜẞçàéâœ.txt';
my $text     = 'TäöüßÄÖÜẞçàéâœ';
my $dirname  = 'DäöüßÄÖÜẞçàéâœ';


# list all files in the parent directory before any action -------------

listDirectory('.');

# create file and write into file --------------------------------------

print "Going to open file $filename for writing ... ";
if (open(my $fileHandle, '>', $filename)) {
    print "done successfully\n";
    print "Going to write text '$text' into file $filename ... ";
    if (print $fileHandle $text."\n") {
        print "done successfully\n";
    } else {
        errorExit("failed to write into file", __LINE__);
    }
    close($fileHandle);
} else {
    errorExit("failed to open file for writing", __LINE__);
}

# create a new directory -----------------------------------------------

print "Going to create directory $dirname ... ";
if (mkdir($dirname)) {
    print "done successfully\n";
} else {
    errorExit("failed to create directory", __LINE__);
}

# list all files in the parent directory again -------------------------

listDirectory('.');

# read file ------------------------------------------------------------

print "Going to open file $filename for reading ... ";
if (open(my $fileHandle, '<', $filename)) {
    print "done successfully\n";
    print "Going to list content of file $filename:\n";
    print "--- begin of content ---\n";
    while (my $row = <$fileHandle>) {
        chomp $row;
        print "$row\n";
    }
    print "--- end of content ---\n\n";
    close($fileHandle);
} else {
    errorExit("failed to open file for reading", __LINE__);
}

# list all files in the newly created directory ------------------------

listDirectory($dirname);

# end ------------------------------------------------------------------

print "normal end of execution\n";
exit(0);

# subroutines ==========================================================


# list all files in a directory ----------------------------------------

sub listDirectory {
    my $dir = shift;
    my $dirname = $dir eq '.' ? 'parent directory' : $dir;
    print "Content of $dirname\n";
    if (opendir (my $dirHandle, $dir)) {
        print "--- begin of content of $dirname ---\n";
        while (my $file = readdir($dirHandle)) {
            print "$file\n";
        }
        print "--- end of content of $dirname ---\n\n";
        closedir($dirHandle);
    } else {
        errorExit("failed to open $dirname", __LINE__);
    }
}

# Error exit -----------------------------------------------------------

sub errorExit {
    my $message = shift;
    my $line = shift;
    print "Error before line $line:\n";
    print "program message: $message\n";
    print "system message: $!\n";
    print "premature end of execution\n";
    exit(0);
}

Вывод моей программы в macOS и в Linux (Ubuntu):

Content of parent directory
--- begin of content of parent directory ---
.
..
testUmlaut.pl
--- end of content of parent directory ---

Going to open file FäöüßÄÖÜẞçàéâœ.txt for writing ... done successfully
Going to write text 'TäöüßÄÖÜẞçàéâœ' into file FäöüßÄÖÜẞçàéâœ.txt ... done successfully
Going to create directory DäöüßÄÖÜẞçàé✠... done successfully
Content of parent directory
--- begin of content of parent directory ---
.
..
testUmlaut.pl
FäöüßÄÖÜẞçàéâœ.txt
DäöüßÄÖÜẞçàéâœ
--- end of content of parent directory ---

Going to open file FäöüßÄÖÜẞçàéâœ.txt for reading ... done successfully
Going to list content of file FäöüßÄÖÜẞçàéâœ.txt:
--- begin of content ---
TäöüßÄÖÜẞçàéâœ
--- end of content ---

Content of DäöüßÄÖÜẞçàéâœ
--- begin of content of DäöüßÄÖÜẞçàé✠---
.
..
--- end of content of DäöüßÄÖÜẞçàé✠---

normal end of execution

Это ожидаемый вывод.

Но я получите это, когда я выполню эту программу на windows машине:

Content of parent directory
--- begin of content of parent directory ---
.
..
testUmlaut.pl
--- end of content of parent directory ---

Going to open file F├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô.txt for writing ... done successfully
Going to write text 'T├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô' into file F├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô.txt ... done successfully
Going to create directory D├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô ... done successfully
Content of parent directory
--- begin of content of parent directory ---
.
..
testUmlaut.pl
F├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô.txt
D├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô
--- end of content of parent directory ---

Going to open file F├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô.txt for reading ... done successfully
Going to list content of file F├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô.txt:
--- begin of content ---
T├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô
--- end of content ---

Content of D├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô
--- begin of content of D├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô ---
.
..
--- end of content of D├ñ├Â├╝├ƒ├ä├û├£ß║×├º├á├®├ó┼ô ---

normal end of execution

Итак, все имена файлов написаны с неправильной кодировкой. Также в проводнике вы видите неправильно закодированные имена файлов для нового файла и каталога. Но хотя текстовый файл содержит правильное содержимое, моя программа отображает его неправильно.

Итак, я возился с моей программой, пока не получил версию, которая выдает правильный вывод (идентичный выводу первого наивного). версия под macOS и Linux)).

Но в файловой системе имена файлов по-прежнему неверны:

13.01.2020  17:36    <DIR>          .
10.01.2020  14:46    <DIR>          ..
13.01.2020  18:23             2 970 testUmlaut.pl
13.01.2020  18:23                30 FäöüßÄÖÜẞçà éâœ.txt
13.01.2020  18:23    <DIR>          DäöüßÄÖÜẞçà éâœ

Вот код новой версии моей программы:

#!/usr/bin/perl -w

use strict;
use warnings;
use utf8;
use Encode;
if ($^O eq 'MSWin32') {
    require Win32::Console;
    Win32::Console::OutputCP(65001);
}

binmode STDOUT, ":utf8";

my $filename = 'FäöüßÄÖÜẞçàéâœ.txt';
my $text     = 'TäöüßÄÖÜẞçàéâœ';
my $dirname  = 'DäöüßÄÖÜẞçàéâœ';


# list all files in the parent directory before any action -------------

listDirectory('.');

# create file and write into file --------------------------------------

print "Going to open file $filename for writing ... ";
if (open(my $fileHandle, '>:encoding(UTF-8)', $filename)) {
    print "done successfully\n";
    print "Going to write text '$text' into file $filename ... ";
    if (print $fileHandle $text."\n") {
        print "done successfully\n";
    } else {
        errorExit("failed to write into file", __LINE__);
    }
    close($fileHandle);
} else {
    errorExit("failed to open file for writing", __LINE__);
}

# create a new directory -----------------------------------------------

print "Going to create directory $dirname ... ";
if (mkdir($dirname)) {
    print "done successfully\n";
} else {
    errorExit("failed to create directory", __LINE__);
}

# list all files in the parent directory again -------------------------

listDirectory('.');

# read file ------------------------------------------------------------

print "Going to open file $filename for reading ... ";
if (open(my $fileHandle, '<:encoding(UTF-8)', $filename)) {
    print "done successfully\n";
    print "Going to list content of file $filename:\n";
    print "--- begin of content ---\n";
    while (my $row = <$fileHandle>) {
        chomp $row;
        print "$row\n";
    }
    print "--- end of content ---\n\n";
    close($fileHandle);
} else {
    errorExit("failed to open file for reading", __LINE__);
}

# list all files in the newly created directory ------------------------

listDirectory($dirname);

# end ------------------------------------------------------------------

print "normal end of execution\n";
exit(0);

# subroutines ==========================================================


# list all files in a directory ----------------------------------------

sub listDirectory {
    my $dir = shift;
    my $dirname = $dir eq '.' ? 'parent directory' : $dir;
    print "Content of $dirname\n";
    if (opendir (my $dirHandle, $dir)) {
        print "--- begin of content of $dirname ---\n";
        while (my $file = decode_utf8(readdir($dirHandle))) {
            print "$file\n";
        }
        print "--- end of content of $dirname ---\n\n";
        closedir($dirHandle);
    } else {
        errorExit("failed to open $dirname", __LINE__);
    }
}

# Error exit -----------------------------------------------------------

sub errorExit {
    my $message = shift;
    my $line = shift;
    print "Error before line $line:\n";
    print "program message: $message\n";
    print "system message: $!\n";
    print "premature end of execution\n";
    exit(0);
}

Эта новая версия по-прежнему работает хорошо при работе в Linux или macOS. Но все еще есть проблема с именами файлов в Windows.

Как я могу это исправить?

1 Ответ

5 голосов
/ 13 января 2020

Windows системные вызовы, которые принимают / возвращают строку, бывают двух разновидностей. Версия «A» (ANSI), которая работает с текстом, закодированным с использованием активной кодовой страницы системы, и версия «W» (Wide), которая работает с текстом, закодированным с использованием UTF-16le.

Perl использует Только для версии «A», и, таким образом, ожидается, что имена файлов будут закодированы с использованием активной кодовой страницы (например, cp1252 для большинства машин в США.)

Одним из решений является кодирование имени файла с использованием правильная кодовая страница.

use utf8;  # Source code encoded using UTF-8.

my ($cie, $coe, $ae);    
BEGIN {
   require Win32;
   $cie = "cp" . Win32::GetConsoleCP();
   $coe = "cp" . Win32::GetConsoleOutputCP();
   $ae  = "cp" . Win32::GetACP();

   binmode(STDIN,  ":encoding($cie)");
   binmode(STDOUT, ":encoding($coe)");
   binmode(STDERR, ":encoding($coe)");

   require "open.pm";
   "open"->import(":encoding($ae)");  # Default encoding for open()
}

use Encode qw( encode );

#my $qfn = 'FäöüßÄÖÜẞçàéâœ.txt';
my $qfn = 'FäöüßÄÖÜßçàéâœ.txt';

open(my $fh, '>', encode($ae, $qfn))
   or die("Can't create \"$qfn\": $!\n");

print($fh "This is \"$qfn\".\n");

Обратите внимание, что я заменил «ẞ» на «ß», потому что «ẞ» отсутствует в наборе символов моей активной кодовой страницы (cp1252), и поэтому не мог использоваться как часть имени файла. Чтобы избежать этой проблемы, нужно использовать широкий интерфейс. Это может быть достигнуто с помощью Win32 :: Unicode :: File и Win32 :: Unicode :: Dir или Win32 :: LongPath .

use utf8;  # Source code encoded using UTF-8.

my ($cie, $coe, $ae);    
BEGIN {
   require Win32;
   $cie = "cp" . Win32::GetConsoleCP();
   $coe = "cp" . Win32::GetConsoleOutputCP();
   $ae  = "cp" . Win32::GetACP();

   binmode(STDIN,  ":encoding($cie)");
   binmode(STDOUT, ":encoding($coe)");
   binmode(STDERR, ":encoding($coe)");

   require "open.pm";
   "open"->import(":encoding($ae)");  # Default encoding for open()
}

use Win32::Unicode::File qw( );

my $qfn = 'FäöüßÄÖÜẞçàéâœ.txt';

my $fh = Win32::Unicode::File->new('>', $qfn)
   or die("Can't create \"$qfn\": $!\n");

binmode($fh, ":encoding($ae)");  # Didn't happen automatically since we didn't use open()

print($fh "This is \"$qfn\".\n");

Подробнее об этом читайте здесь .

...