Как мне скопировать файл с именем UTF-8 в другое имя файла UTF-8 в Perl в Windows?

Например, учитывая пустой файл テスト.txt, как мне сделать копию с именем テスト.txt.copy?

Моя первая попытка взломать его удалось получить доступ к файлу и создать новое имя файла, но копия сгенерировала テスト.txt.copy.

Вот моя первая попытка:

#!/usr/bin/env perl

use strict;
use warnings;

use English '-no_match_vars';
use File::Basename;
use Getopt::Long;

use File::Copy;
use Win32;

my (
    $output_relfilepath,
   ) = process_command_line();

open my $fh, '>', $output_relfilepath or die $!;
binmode $fh, ':utf8';
foreach my $short_basename ( glob( '*.txt') ) {

  # skip the output basename if it's in the glob
  if ( $short_basename eq $output_relfilepath ) {
    next;
  }

  my $long_basename = Win32::GetLongPathName( $short_basename );
  my $new_basename  = $long_basename . '.copy';

  print {$fh} sprintf(
                      "short_basename = (%s)\n" .
                      " long_basename = (%s)\n" .
                      "  new_basename = (%s)\n",
                      $short_basename,
                      $long_basename,
                      $new_basename,
                     );
  copy( $short_basename, $new_basename );
}

printf(
       "\n%s done! (%d seconds elapsed)\n",
       basename( $0 ),
       time() - $BASETIME,
      );

# === subroutines ===

sub process_command_line {

  # default arguments
  my %args
    = (
       output_relfilepath => 'output.txt',
      );

  GetOptions(
             'help'                 => sub { print usage(); exit },
             'output_relfilepath=s' => \$args{output_relfilepath},
            );

  return (
          $args{output_relfilepath},
         );
}

sub usage {
  my $script_name = basename $0;

  my $usage = <<END_USAGE;
======================================================================

Test script to copy files with a UTF-8 filenames to files with
different UTF-8 filenames.  This example tries to make copies of all
.txt files with versions that end in .txt.copy.

  usage: ${script_name} (<options>)

options:

  -output_relfilepath <s>   set the output relative file path to <s>.
                            this file contains the short, long, and
                            new basenames.
                            (default: 'output.txt')

----------------------------------------------------------------------

examples:

  ${script_name}

======================================================================
END_USAGE

  return $usage;
}

Вот содержимое output.txt после выполнения:

short_basename = (BD9A~1.TXT)
 long_basename = (テスト.txt)
  new_basename = (テスト.txt.copy)

Я попытался заменить команду копирования File :: Copy системным вызовом:

my $cmd = "copy \"${short_basename}\" \"${new_basename}\"";
print `$cmd`;

и с Win32 :: CopyFile:

Win32::CopyFile( $short_basename, $new_basename, 'true' );

К сожалению, в обоих случаях я получаю одинаковый результат (テスト.txt.copy). Для системного вызова печать показывает 1 file(s) copied., как и ожидалось.

Примечания:


person vlee    schedule 21.02.2010    source источник
comment
Какая кодировка по умолчанию установлена ​​в вашей Windows? EUC-JP? Shift_JIS?   -  person Mike    schedule 21.02.2010
comment
См. Также stackoverflow.com/questions/2184726/   -  person Sinan Ünür    schedule 21.02.2010
comment
Майк: Из общего чтения и использования binmode ': utf8' Я думаю, что моя кодировка по умолчанию - utf-8, но я не уверен на 100%. Синан: Спасибо за ссылку!   -  person vlee    schedule 21.02.2010
comment
@vleeshue: Прочтите сообщение, на которое вас указал Синан - мне удалось читать / записывать файлы / каталоги, используя символы Unicode в их именах в Windows, используя его подход.   -  person Nele Kosog    schedule 23.02.2010


Ответы (5)


Это должно быть возможно с помощью функции CopyFileW из Win32API :: File, который должен быть включен в Strawberry. Я никогда не связывался с именами файлов Unicode, поэтому я не уверен в деталях. Возможно, вам потребуется использовать Encode, чтобы вручную преобразовать имя файла в UTF-16LE (encode('UTF16-LE', $filename)).

person cjm    schedule 21.02.2010
comment
Выглядит хорошо. CopyFileW, безусловно, является базовым системным вызовом, который вам понадобится для этого; досадно, что это не часть модуля Win32. - person bobince; 21.02.2010

Вы получаете длинное имя файла с использованием Win32, что дает вам строку в кодировке UTF-8.

Однако затем вы устанавливаете длинное имя файла, используя простой copy, который использует функции ввода-вывода C stdlib. Функции stdlib используют кодировку файловой системы по умолчанию.

В современных Linux это обычно UTF-8, но в Windows (к сожалению) никогда не бывает, потому что системная кодовая страница по умолчанию не может быть установлена ​​на UTF-8. Таким образом, вы получите свою строку UTF-8, интерпретируемую как строку кодовой страницы 1252 при установке Windows в Западной Европе, как это произошло здесь. (На японской машине это будет интерпретироваться как кодовая страница 932 - например, Shift-JIS - что-то вроде 繝�せ繝�.)

Я не делал этого в Perl, но подозреваю, что функция Win32::CopyFile с большей вероятностью сможет обрабатывать пути Unicode, возвращаемые где-то еще в модуле Win32.

person bobince    schedule 21.02.2010
comment
Спасибо за информацию. Я также пробовал как стандартную копию Windows (системный вызов), так и Win32 :: CopyFile, но безрезультатно (обновил вопрос с новыми выводами). Я просто (наивно?) Удивляюсь, насколько это сложно :( - person vlee; 21.02.2010
comment
О, Боже. Если даже Win32 интерфейс не принимает имена файлов в Юникоде, вы можете быть в значительной степени сбиты с толку. Да, я боюсь, что комбинация Windows с собственным Unicode и байтовой строкой C stdlib очень неудобна из-за отказа Windows стандартизировать кодировку UTF-8. Невозможно обрабатывать имена файлов Unicode из интерфейса только для stdlib, такого как ядро ​​Perl. :-( Это было невозможно и на Python, пока не была добавлена ​​специальная поддержка для использования собственных интерфейсов Windows. Извините! - person bobince; 21.02.2010

Используйте Encode :: Locale:

use Encode::Locale;
use Encode;
use File::Copy;

copy( encode(locale_fs => $short_basename),
      encode(locale_fs => $new_basename) ) || die $!;
person godegisel    schedule 04.05.2012

Я успешно продублировал вашу проблему на моем компьютере с Windows (версия Win XP Simplified Chinese) и пришел к выводу, что проблема вызвана шрифтом. Выберите шрифт Truetype, а не растровые шрифты, и посмотрите, все ли в порядке.

Мой эксперимент таков:

  1. Сначала я изменил кодовую страницу своей консоли Windows с 936 (GBK) по умолчанию на 65001 (UTF-8). набрав C:> chcp 65001

  2. Я написал скрипт, содержащий код: $ a = "テ ス ト"; print $ a; и сохранил его как UTF-8.

  3. Я запустил сценарий из консоли и обнаружил, что «テ ス ト» превратилось в «ãƒ † スト», что является точно таким же признаком, который вы описали в своем вопросе.

  4. Я изменил шрифт консоли с растровых шрифтов на консоль Lucida, экран консоли дал мне это: «テ ス ト ス ト ト ト», что все еще не совсем верно, но я предполагаю, что это приближается к сути проблемы.

Хотя я не уверен на 100%, но проблема, вероятно, вызвана шрифтом.

Надеюсь это поможет.

person Mike    schedule 23.02.2010

См. https://metacpan.org/pod/Win32::Unicode.

#!/usr/bin/perl --
use utf8;
use strict;
use warnings;

my @kebabs = (
  "\x{45B}\x{435}\x{432}\x{430}\x{43F}.txt",               ## ћевап.txt
  "ra\x{17E}nji\x{107}.txt",                               ## ražnjić.txt
  "\x{107}evap.txt",                                       ## ćevap.txt
  "\x{43A}\x{435}\x{431}\x{430}\x{43F}\x{447}\x{435}.txt", ## кебапче.txt
  "kebab.txt",
);

{
    use Win32::Unicode qw/ -native /;
    printW "I \x{2665} Perl"; # unicode console out
    mkpathW 'meat';
    chdirW 'meat';
    for my $kebab ( @kebabs ){
        printW "kebabing the $kebab\n";
        open my($fh), '>:raw', $kebab or dieW Fudge($kebab);
        print $fh $kebab              or dieW Fudge($kebab);
        close $fh                     or dieW Fudge($kebab);
    }
}

sub Fudge {
    use Errno();
    join qq/\n/,
      "Error @_",
      map { "  $_" } int( $! ) . q/ / . $!,
      int( $^E ) . q/ / . $^E,
      grep( { $!{$_} } keys %! ),
      q/ /;
}
person optional    schedule 17.01.2015