【问题标题】:Unicode error only when code run with -n flag at the command-line仅当代码在命令行中使用 -n 标志运行时出现 Unicode 错误
【发布时间】:2016-01-18 21:46:07
【问题描述】:

以下简单脚本(基本上)在其输入中进行 slurp,根据正则表达式对其进行拆分,替换结果列表中每个元素中的所有换行符,并逐个打印出修改后的元素:

# demo.pl
use strict;
use utf8;
use open qw(:std :utf8);
use warnings qw(FATAL utf8);

BEGIN { $/ = $\ = undef; }

while ( <> ) {
  s/\n\z//;
  s/\n/\\n/g, print "$_\n" for split /\n(?=[^\W\d]\w*=)/;
}

当给定输入文件 (INPUTFILE) 时,其参数包含以下(UTF8 编码)内容

A=42
ΦΡΩΒΩΖΖ=ABCDEFGHIJKLMNOPQRSTUVWXYZ
_B_C_D12=
foo
345bar=nope
baz
  =whatever=
X_Y_Z=quux

...它打印出所需的输出,即:

% perl demo.pl INPUTFILE
A=42
ΦΡΩΒΩΖΖ=ABCDEFGHIJKLMNOPQRSTUVWXYZ
_B_C_D12=\nfoo\n345bar=nope\nbaz\n  =whatever=
X_Y_Z=quux

相比之下,以下几乎相同的 CLI 单行代码

% perl -ne 'use strict; use utf8; use open qw(:std :utf8); use warnings qw(FATAL utf8); BEGIN { $/ = $\ = undef; } s/\n\z//; s/\n/\\n/g, print "$_\n" for split /\n(?=[^\W\d]\w*=)/;' INPUTFILE

...为相同的输入文件生成以下内容

A=42\nΦΡΩÎΩÎÎ=ABCDEFGHIJKLMNOPQRSTUVWXYZ
_B_C_D12=\nfoo\n345bar=nope\nbaz\n  =whatever=
X_Y_Z=quux

这里(显然)有两个问题:

  1. 正则表达式无法分隔第一项和第二项;
  2. 输出包含难以辨认的子字符串。

(我希望这两个问题都有相同的根本原因。)


“文件内脚本”(demo.pl) 和 CLI 单行之间的唯一区别是前者明确地用while ( &lt;&gt; ) { ... } 包裹脚本的主体,而对于后者,-n flag 导致这个包装器被自动插入。

问:必须如何修改上面的单行代码,以便产生所需的结果带有-n 标志


顺便说一句,demo.pl(没有 -n 标志)的 exact 命令行等价物也就不足为奇了,即

% perl -e 'use strict; use utf8; use open qw(:std :utf8); use warnings qw(FATAL utf8); BEGIN { $/ = $\ = undef; } while ( <> ) { s/\n\z//; s/\n/\\n/g, print "$_\n" for split /\n(?=[^\W\d]\w*=)/; }' INPUTFILE

还会产生所需的输出。

所以无论是什么问题,都与-n 标志有关。


FWIW:

% perl -v | head -2

This is perl 5, version 20, subversion 2 (v5.20.2) built for x86_64-linux-gnu-thread-multi

编辑:还有一条线索:如果失败的单线的输入是通过STDIN 而不是@ARGV 中的文件名(例如,用&lt; INPUTFILE 替换INPUTFILE),那么它会产生所需的输出完全清晰,虽然仍然不正确,输出:

A=42\nΦΡΩΒΩΖΖ=ABCDEFGHIJKLMNOPQRSTUVWXYZ
_B_C_D12=\nfoo\n345bar=nope\nbaz\n  =whatever=
X_Y_Z=quux

我目前的猜测是use open qw(:std :utf8) 不包括&lt;&gt;@ARGV 中将输入作为文件名传递时从中读取的输入流。

【问题讨论】:

    标签: regex unicode command-line perl


    【解决方案1】:

    “文件内脚本”(demo.pl) 和 CLI 单行程序之间的唯一区别是前者用 while ( ) { ... } 显式包装脚本主体,而对于后者,-n 标志会导致这个包装器被自动插入。

    是的,完全正确 - -n 包装了 所有 while (&lt;&gt;) { ... } 中的代码。这包括您的 use utf8;use open(:utf8); 行,因此在您启用 Unicode 时文件已经打开

    您可以通过运行与-n 版本等效的程序来轻松验证这一点:

    while (<>) {
        # demo.pl
        use strict;
        use utf8;
        use open qw(:std :utf8);
        use warnings qw(FATAL utf8);
        BEGIN { $/ = $\ = undef; }
        s/\n\z//;
        s/\n/\\n/g, print "$_\n" for split /\n(?=[^\W\d]\w*=)/;
    }
    

    看到同样的效果。


    更有趣的是,您可以看到use 声明确实仍然有效:将完全相同的输入文件运行两次

    perl demo.pl INPUTFILE INPUTFILE
    

    你得到两个输出,第一个坏了,第二个正确。这也发生在您的单线器上。


    您可以使用-C flag with the i (8) 选项默认启用 UTF-8 输入:

    perl -CiO -ne 'use strict; use utf8; BEGIN { $/ = $\ = undef; } s/\n\z//; s/\n/\\n/g, print "$_\n" for split /\n(?=[^\W\d]\w*=)/;' INPUTFILE
    

    这可确保在打开文件之前启用 UTF-8,并获得正确的输出。 O 也为标准输出启用了 UTF-8,以便您可以打印它。

    【讨论】:

    • 这令人困惑。我的理解是use ... 意味着BEGIN { ... }。我认为这意味着它会在处理任何其余代码之前发生......
    • 谢谢。 FWIW,由于use warnings qw(FATAL utf8) 以及输出中存在宽字符,您仅使用-Ci(而不是-CiO)显示的版本在问题中给出的输入失败。摆脱use warnings qw(FATAL utf8) 允许程序完成,但仍会打印警告。底线:必须是-CiO,而不是-Ci
    • use 有时有点神奇。关于警告的要点 - 现在已编辑。
    • OP:请注意,您还可以在 CLI 中包含 -e|-E 开关之前的所需模块:perl -Mstrict -Mopen=:std,:utf8 -Mwarnings=FATAL,utf8 -e 'BEGIN{...。 (作为评论发布,不要从这个答案中删除)。
    猜你喜欢
    • 2013-07-16
    • 1970-01-01
    • 2020-09-27
    • 1970-01-01
    • 2019-09-08
    • 1970-01-01
    • 2013-11-15
    • 1970-01-01
    • 2013-06-05
    相关资源
    最近更新 更多