为什么我不能在Perl的tr///表达式中使用引号?

4
我想在文档中将弯曲的单引号和双引号转换为中性引号。我以为这应该很简单,就像perl -pe 'tr/“”’/""\047/'一样,但是这并不起作用。例如:
snafu$ echo '“' | perl -pe 'tr/“”’/""\047/'
""'
snafu$ echo '“”’' | perl -pe 'tr/“”’/""\047/'
""'""'""'

请注意,一个双引号“在右侧成为了一整套字符。在第二个例子中,这种情况发生了三次。
而且,更令人意外的是,即使对于这个微不足道的情况,三重现象也会发生。
snafu$ echo '“' | perl -pe 'tr/“/"/'
"""

这种行为似乎与我在ASCII字符中看到的非常不同,像这样:

snafu$ echo "Larry Wall" | perl -pe 'tr/ay/AY/'
LArrY WAll

我还尝试了使用perl -Mutf8进行调用,但结果并不如我所预期的那样。
# not triplicated, but also not transliterated
snafu$ echo '“' | perl -Mutf8 -pe 'tr/“”’/""\047/'

什么解释了tr///的上述行为?
2个回答

3
你想要以下内容:
perl -CS -Mutf8 -pe 'tr/“”’/""\047/'

没有使用 use utf8;,Perl 期望源代码使用 ASCII 编码。因此,你的第一个代码片段不可能包含 。由于字符串字面量是“8 位清洁”的,所以你的第一个代码片段等同于

tr/\xE2\x80\x9C\xE2\x80\x9D\xE2\x80\x99/""'/

那显然是错误的。要修复这个问题,就像你在上一个片段中做的那样,添加use utf8;
所以为什么最后一段代码片段不起作用呢?那是因为它实际上在做...
"\xE2\x80\x9C" =~ tr/\x{201C}\x{201D}\x{2019}/""'/;

这显然也是错误的。你正在搜索编码文本(UTF-8字节串)来寻找解码文本(Unicode代码点串)。你需要对输入进行解码,对输出进行编码。可以使用use open ":std", ":encoding(UTF-8)";来实现,但在这里也可以使用-CS
终于,这里有
echo '“' | perl -pe 'tr/“/"/'

从上面我们知道它等同于以下内容:
"\xE2\x80\x9C" =~ tr/\xE2\x80\x9C/"/;

除非您使用 /d,否则如果右侧字符少于左侧字符,tr/// 将重复最后一个字符。这使得上述等同于

"\xE2\x80\x9C" =~ tr/\xE2\x80\x9C/"""/;

这就解释了输出的"""

2

为了解释你的较简单示例

snafu$ echo '“' | perl -pe 'tr/“/"/'
"""

UTF-8对应于的序列是e2 80 9c。因为所有内容都被视为ASCII字符(字节),您的翻译命令将把每个替换为",这就是为什么会出现三个双引号的原因。

在您的第一个示例中发生了类似的情况。但是由于搜索字符串有9个ASCII字符,而替换字符串只有3个,因此只考虑映射的替换。所有字符的前两个UTF-8字节(“”’)相同,因此当作为ASCII处理时,它们映射到替换字符串中的前两个字符。然后,的第三个字节映射到替换字符串中的第三个字符。但是其他两个字符的第三个字节没有映射,并且被替换为替换字符串的最后一个字符。如果您在替换字符串中添加第四个字符,您可以更清楚地看到这一点。例如,如果输入为“”’,则tr/“”’/""\047z/将输出""'""z""z

您的代码没有问题。如果您将脚本写入文件并正确使用utf8binmode,它将按预期工作:

use utf8;
binmode STDIN, ":utf8";
my $s = <STDIN>;
$s =~ tr/“”’/""'/;
print "$s";

所以你需要告诉Perl从命令行将STDIN作为UTF-8处理。你可以使用-C1或更常见的选项-CS来实现,这将把STDIN、STDOUT和STDERR都视为UTF-8。
`echo '“”’' | perl -Mutf8 -CS -pe 'tr/“”’/""\047/'

你不应该使用:utf8;你应该使用:encoding(UTF-8) - ikegami
注意:-CS 是对待 utf8 的方式(Perl 特定的扩展),而不是 UTF-8(标准)。 - ikegami
使用-CS是个明智的选择,因为-C1无论如何都不够用。那只会处理标准输入,但你还需要处理标准输出。这意味着你至少需要-C3,也就是-CIO。你可以使用“é”进行测试。 - ikegami
我删除了关于1是STDOUT的混淆评论。 -C数字是一个位掩码(1:STDIN,2:STDOUT,4:STDERR),而不是对文件描述符的引用。(如果是的话,解码STDIN应该是-C0,而不是-C1!) - ikegami

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