Perl中的Unicode字符串混乱问题

4

我有一个外部模块,该模块返回一些字符串。我不确定字符串是如何返回的。我真的不知道Unicode字符串的工作原理和原因。

例如,该模块应返回捷克语单词"být",意思是"to be"。(如果您看不到第二个字母-它应该看起来像this。)如果我使用Data Dumper显示由模块返回的字符串,我会看到b\x{fd}t

但是,如果我尝试使用print $s打印它,我会得到"在打印中宽字符"的警告,并且会用?代替ý。

如果我尝试Encode::decode(whatever, $s);,生成的字符串无论我在whatever中输入什么,都无法打印(始终带有“宽字符”警告,有时是乱码,有时是正确的)。

如果我尝试Encode::encode("utf-8", $s);,则生成的字符串可以正常打印,没有问题或错误消息。

如果我使用use encoding 'utf8';,那么打印输出就不需要任何编码/解码。然而,如果我使用IO::CaptureOutputCapture::Tiny模块,它会再次报出“Wide character”的错误。
我有一些问题,主要是关于到底发生了什么。 (我尝试阅读过perldocs,但是我并没有从中获得太多智慧)
  1. 为什么我不能在从模块获取字符串后立即打印它?
  2. 为什么我无法打印经过“decode”解码的字符串?“decode”到底做了什么?
  3. “encode”到底做了什么,为什么在编码后打印时没有问题?
  4. use encoding到底做了什么?为什么默认编码与utf-8不同?
  5. 如果我想要在使用其中一个捕获模块时打印标量而不会出现任何问题,我该怎么办?

编辑:有些人告诉我使用-CbinmodePERL_UNICODE。这是一个很好的建议。然而,不知何故,两个捕获模块神奇地破坏了STDOUT的UTF8性质。这似乎更像是模块的一个错误,但我不确定。

编辑2:好吧,最好的解决方案是放弃这些模块,自己编写“捕获”(灵活性要少得多)。


你能展示一个演示问题的脚本示例吗?有很多要注意的细节,因此从具体的东西开始工作会更容易。 - brian d foy
1
当你有了简短的示例脚本后,使用它向 RT 上的那些模块报告错误。 - brian d foy
3个回答

5
  1. 因为您在perl的内部形式(utf8)中输出字符串到非unicode文件句柄。
  2. decode函数将假定为ENCODING格式的字节序列解码为Perl的内部形式(utf8)。您的输入似乎已经解码过了,
  3. encode()函数将从Perl的内部形式编码成ENCODING格式的字符串。
  4. encoding pragma允许您以任何编码方式编写脚本。字符串文字会自动转换为perl的内部形式。
  5. 确保perl知道您的数据输入和输出采用哪种编码。

另请参阅perluniintro、perlunicode、Encode模块、binmode()函数。


非常感谢。我该如何打开STDOUT,使其成为一个Unicode文件句柄?为什么它不是默认的呢?编辑:哦,我明白了,这是二进制模式。第二个“子问题”仍然存在。为什么STDOUT的二进制模式不是默认的UTF-8? - Karel Bílek
1
@Karel Bilek:可能不是默认设置,因为这会导致向后兼容性问题。不过Perl6做得很好。 - Daenyth
不幸的是,它不起作用。捕获函数(两个函数)会使STDOUT的binmode再次变为非UTF8格式,从而破坏它。 - Karel Bílek
1
@Karel Bílek:因为当它是默认设置时(仅在5.8.0版本中,并且仅当用户的语言环境是utf8时,如果我没记错的话),它会破坏很多人的东西。 - ysth

3

我建议阅读我书中的Unicode章节《高效Perl编程》。我们收集了所有可以找到的文档,并以比我在其他任何地方看到的更连贯的方式解释了Perl中的Unicode。

这个程序对我来说运行良好:

#!perl

use utf8;
use 5.010;

binmode STDOUT, ':utf8';

my $string = return_string();

say $string;

sub return_string { 'být' }

此外,Capture::Tiny 对我来说完全正常:
#!perl
use utf8;
use 5.010;
use Capture::Tiny qw(capture);

binmode STDOUT, ':utf8';

my( $stdout, $stderr ) = capture {
    system( $^X, '/Users/brian/Desktop/czech.pl' );
    };

say "STDOUT is [$stdout]";

IO::CaptureOutput似乎存在一些问题:

#!perl
use utf8;
use 5.010;
use IO::CaptureOutput qw(capture);

binmode STDOUT, ':utf8';

capture {
    system( $^X, '/Users/brian/Desktop/czech.pl' );
    } \my $stdout, \my $stderr;

say "STDOUT is [$stdout]";

对此,我得到:
STDOUT is [být
]

不过,这很容易解决。不要使用那个模块。 :)


就我个人而言,IO::CaptureOutput对我来说“可行”。但是我认为它有缺陷,你获得的输出是正确的。qx//按照我认为正确的方式工作,默认情况下捕获5个字符(包括换行符),当指定use open IN=>":utf8";时,捕获4个字符。 - ysth

1
你还应该查看PERL_UNICODE环境变量,它与使用-C选项相同。这允许您将STDIN/STDOUT/STDERR(以及@ARGV)设置为UTF-8,而无需更改脚本。

不行。即使使用“-C”选项也无法在捕获函数中保留。但这似乎更多是函数本身的问题,而不是Perl的问题。我猜。 - Karel Bílek
好的。最简单的解决方法是自己编写函数(灵活性较低)。谢谢 :) - Karel Bílek
@Karel Bílek,你在使用“-C”时使用了什么值?正如我链接的文档中所解释的那样,有许多可能的设置。 - cjm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接