我正在尝试为 Perl 脚本构建一个模板,以便它们至少可以使用 UTF-8 正确完成大部分基本操作,并且在 Linux 和 Windows 机器上同样可以正常工作。
我有一段时间特别忽略了一件事:将 UTF-8 字符串作为参数传递给系统命令的困难。在我看来,在参数到达 shell 之前,没有办法不对其进行双重 UTF-8 编码(也就是说,我知道有一个层忽略命令及其参数已经正确地进行了 UTF-8 编码,将其视为 Latin-1 或类似的东西,并再次将其编码为 UTF-8)。我找不到一种方法来彻底避免这一层编码。
采用此脚本:
#!/usr/bin/perl
use v5.14;
use utf8;
use feature 'unicode_strings';
use feature 'fc';
use open ':std', ':encoding(UTF-8)';
use strict;
use warnings;
use warnings FATAL => 'utf8';
use constant IS_WINDOWS => $^O eq 'MSWin32';
# Set proper locale
$ENV{'LC_ALL'} = 'C.UTF-8';
# Set UTF-8 code page on Windows
if (IS_WINDOWS) {
system("chcp 65001 > nul 2>&1");
};
# Use Win32::Unicode::Process on Windows
if (IS_WINDOWS) {
eval {
require Win32::Unicode::Process;
Win32::Unicode::Process->import;
};
if ($@) {
die "Could not load Win32::Unicode::Process: $@";
};
};
# Show the empty directory
print "---\n" . `ls -1 system*` . "---\n";
my $utf = "test-тест-מבחן-परीक्षण-😊-𝓽𝓮𝓼𝓽";
# Works fine on Linux but not on Windows
print "System (touch) exit code: " . system("touch system-$utf > touch-system.txt 2>&1") . "\n";
print "System (echo) exit code: " . system("echo system-$utf > echo-system.txt 2>&1") . "\n";
if (IS_WINDOWS) {
# Works fine on Windows
print "SystemW (touch) exit code: " . systemW("touch systemW-$utf > touch-systemW.txt 2>&1") . "\n";
print "SystemW (echo) exit code: " . systemW("echo systemW-$utf > echo-systemW.txt 2>&1") . "\n";
};
# Show the directory with the new the files
print "---\n" . `ls -1 system*` . "---\n";
exit;
在 Linux 上,一切都很好:使用
touch
到 system()
创建的文件具有 UTF-8 编码的文件名,并且使用 echo
创建的文件内容是正确的 UTF-8 编码。
然而,我发现没有办法让相同的代码在 Windows 上正确运行。脚本的输出是这样的:
---
---
System (touch) exit code: 0
System (echo) exit code: 0
SystemW (touch) exit code:
SystemW (echo) exit code:
---
system-test-теÑÑ‚-מבחן-परीकà¥à¤·à¤£-😊-ð“½ð“®ð“¼ð“½
systemW-test-тест-מבחן-परीक्षण-😊-𝓽𝓮𝓼𝓽
---
如脚本所示,我可以使其工作的唯一方法是使用
Win32::Unicode::Process::systemW()
替换 system()
。文件 systemW-test-тест-מבחן-परीक्षण-😊-𝓽𝓮𝓼𝓽
已正确命名,并且 echo-systemW.txt
的内容已正确编码为 UTF-8。
我的问题是:
有没有办法避免使用
systemW()
并保持Linux和Windows的代码相同,但以某种方式删除这层对系统命令进行双重编码的层?换句话说,这是唯一的好方法吗?
如果这是正确的方法,我不确定如何获得反引号的类似正确行为。他们有与
system()
相同的问题,但我不知道如何使用 systemW()
捕获命令的输出,除了将其管道到临时文件中并在最后读取(当然,可能,但可能不是很好) ).
避免使用 systemW() 在 Linux 和 Windows 上实现统一行为:不幸的是,Windows 的 cmd.exe 并不像 Linux shell 那样原生支持 UTF-8。即使使用 chcp 65001(将控制台代码页设置为 UTF-8),也存在怪癖和不一致。出现双重编码问题是因为 Windows 上的 Perl system() 函数和反引号 (```) 内部使用 ANSI API,而这些 API 不完全尊重 UTF-8。
为了实现一致的行为,您必须使用宽字符 API,例如 Win32::Unicode::Process 中的 systemW()。 Windows 上的 Perl 标准 system() 没有直接的方法来绕过这个限制。
使用宽字符 API 处理反引号:正如您所发现的,Perl 的反引号也依赖于 ANSI API,并且没有用于捕获输出的 systemW() 的直接等效项。但是,您可以使用以下解决方法:
如您所提到的,使用临时文件进行命令输出。 或者,利用 Win32::Unicode::Process 使用宽字符 API 实现自定义反引号行为。