utf8 似乎打破了 Perl 中的正则表达式规则?



我正在尝试调试为什么我的 UTF-8 在我的脚本中无法正常工作。这是原始代码:

$lc_custom{"À propos de l'italie, en français"} = "foo bar";
$lc_custom{"Здоровье"} = "foo bar";
$lc_custom{"дерьмо"} = "foo bar";
$lc_custom{"sécurité"} = "foo bar";
$lc_custom{"security"} = "foo bar";
$lc_custom{"health"} = "foo bar";
$lc_custom{"french"} = "foo bar";
$lc_custom{"ábc"} = "foo bar";
$lc_custom{"crap"} = "foo bar";
my $text_repl = '| (' . join('|', map { my $v = quotemeta; $v = 'b'.$v if $v =~ /^w/; $v .= 'b' if $v =~ /w$/ } sort { length($b) <=> length($a) } keys %lc_custom) . ')';

我得到的调试:

$VAR1 = {
'foo' => '| (\�\�\ propos\ de\ l\'italie\,\ en\ fran\�\�ais\b||||\bsecurity\b|\bhealth\b|\bfrench\b|\�\�bc\b|\bcrap\b)'
};

这是我的修订版本,其中包含更多调试:

my $text_repl = '| (' . join('|', map {
print "FOO BAR: $_ n";
my $v = $_;
$v = 'b' . $v if $v =~ /^w/;
$v .= 'b' if $v =~ /w$/
} sort { length($b) <=> length($a) } keys %lc_custom) . ')';

我得到:

FOO BAR: À propos de l'italie, en français 
FOO BAR: Здоровье 
FOO BAR: дерьмо 
FOO BAR: sécurité 
FOO BAR: security 
FOO BAR: health 
FOO BAR: french 
FOO BAR: ábc 
FOO BAR: crap 
$VAR1 = {
'foo' => '| (\QÀ propos de l'italie, en français\E\b||||\b\Qsecurity\E\b|\b\Qhealth\E\b|\b\Qfrench\E\b|ábc\E\b|\b\Qcrap\E\b\E)'
};        

似乎所有的键都不喜欢在有俄语的时候工作。有什么原因吗?

更新:根据要求,以下是它的外观:

use utf8;
my $test = '| (' . join('|', map { my $v = quotemeta; $v = 'b'.$v if $v =~ /^w/; $v .= 'b' if $v =~ /w$/ } sort { length($b) <=> length($a) } keys %lc_custom) . ')';
use Data::Dumper;
$Data::Dumper::Useqq = 1;
print Dumper({ BLA => $test });

给:

"BLA" => "| (\303\200\ propos\ de\ l\'italie\,\ en\ fran\303\247ais\b||||\bsecurity\b|\bhealth\b|\bfrench\b|\303\241bc\b|\bcrap\b)"

解码输入;对输出进行编码。问题源于缺乏前者。%lc_custom键是使用 UTF-8 编码的文本字符串。您通常不想使用编码文本;您想要使用解码的文本。

quotemetaw正则表达式字符类都希望提供解码文本。将编码文本传递给它们没有意义。但这就是你正在做的事情。


让我们看一个简单的例子。

use Data::Dumper qw( Dumper );
$Data::Dumper::Useqq = 1;
# "д♠" encoded using UTF-8 (encoded text).
my $utf8 = "320264342231240";
say length($utf8);
print Dumper($utf8);
print Dumper(quotemeta($utf8));
say length(quotemeta($utf8));
say "";
# "д♠" as decoded text (Unicode Code Points).
my $ucp = "x{434}x{2660}";
say length($ucp);
print Dumper($ucp);
print Dumper(quotemeta($ucp));
say length(quotemeta($ucp));
5
$VAR1 = "320264342231240";
$VAR1 = "320264342\231\240";
7
2
$VAR1 = "x{434}x{2660}";
$VAR1 = "x{434}\x{2660}";
3

请注意,quotemeta($utf8)在 "♠" 编码的中间插入了 2 个反斜杠,而在它之前没有。另一方面,quotemeta($ucp)两个字符之间添加了单个反斜杠。

简而言之,您正在将垃圾传递给quotemeta,并且您正在取回垃圾。


Perl 希望它的源代码使用 ASCII 进行编码,除非你告诉它它是使用 UTF-8 通过use utf8;进行编码的。

use 5.014;      # Or: use strict; use feature qw( say unicode_strings );
use warnings;
# Tell Perl the source code is encoded using UTF-8.
use utf8;
# Tell Perl the terminal provides/expects UTF-8.
# Also sets the default for `open`.
use open ':std', ':encoding(UTF-8)';
use Data::Dumper qw( Dumper );
$Data::Dumper::Useqq = 1;
# From the question, verbatim.
my %lc_custom;
$lc_custom{"À propos de l'italie, en français"} = "foo bar";
$lc_custom{"Здоровье"} = "foo bar";
$lc_custom{"дерьмо"} = "foo bar";
$lc_custom{"sécurité"} = "foo bar";
$lc_custom{"security"} = "foo bar";
$lc_custom{"health"} = "foo bar";
$lc_custom{"french"} = "foo bar";
$lc_custom{"ábc"} = "foo bar";
$lc_custom{"crap"} = "foo bar";
# From the question, verbatim.
my $text_repl = '| (' . join('|', map { my $v = quotemeta; $v = 'b'.$v if $v =~ /^w/; $v .= 'b' if $v =~ /w$/ } sort { length($b) <=> length($a) } keys %lc_custom) . ')';
say $text_repl;
print Dumper($text_repl);

输出:

| (bÀ propos de l'italie, en françaisb|bЗдоровьеb|bsécuritéb|bsecurityb|bhealthb|bдерьмоb|bfrenchb|bcrapb|bábcb)
$VAR1 = "| (\bx{c0}\ propos\ de\ l\'italie\,\ en\ franx{e7}ais\b|\bx{417}x{434}x{43e}x{440}x{43e}x{432}x{44c}x{435}\b|\bsx{e9}curitx{e9}\b|\bsecurity\b|\bhealth\b|\bx{434}x{435}x{440}x{44c}x{43c}x{43e}\b|\bfrench\b|\bcrap\b|\bx{e1}bc\b)";

请注意,unicode_strings功能修复了一个可能阻止À匹配w的错误。use 5.014;启用该功能(以及更多功能(。

通常,几乎一发布这个,我就想出了解决方案!所以看起来Perl键不喜欢俄语?我以前从来不需要这样做,所以也许这就是它没有出现的原因。我调整了代码,以便使用带有哈希引用的数组来创建正则表达式:

my $text_repl = '| (';
foreach my $x (@lc_words) {
my $v = quotemeta $x->{word};
$v = 'b' . $v if $v =~ /^w/;
$v .= 'b' if $v =~ /w$/;
$text_repl .= "|$v";
}
$text_repl .= ')';

现在这很完美=(

最新更新