我正在尝试为 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()
除了将其传输到临时文件中并在最后读取它(当然可能,但可能不是很好)。
在 Linux 和 Windows 上避免使用 systemW() 实现统一行为:遗憾的是,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 实现自定义反引号类行为。