【问题标题】:Why do Perl string operations on Unicode characters add garbage to the string?为什么 Perl 对 Unicode 字符的字符串操作会给字符串添加垃圾?
【发布时间】:2010-12-07 00:35:23
【问题描述】:

Perl:

$string =~ s/[áàâã]/a/gi; #This line always prepends an "a"
$string =~ s/[éèêë]/e/gi;
$string =~ s/[úùûü]/u/gi;

这个正则表达式应该将“été”转换成“ete”。相反,它将其转换为“aetae”。换句话说,它为每个匹配的元素添加一个“a”。甚至“à”也被转换为“aa”。

如果我把第一行改成这个

$string =~ s/(á|à|â|ã)/a/gi;

它可以工作,但是......现在它会在每个匹配的元素(如“eetee”)前面添加一个e

即使我找到了合适的解决方案,为什么它会这样?

编辑 1:

我添加了“use utf8;”,但它并没有改变行为(尽管它破坏了我在JavaScript/AJAX 中的输出)。

编辑2:

流源自一个 Ajax 请求,由 jQuery 执行。它的来源站点设置为UTF-8

我正在使用Perl v5.10perl -v 返回“这是 perl,为 i586-linux-thread-multi 构建的 v5.10.0”)。

【问题讨论】:

  • 站点设置的内容会将编码设置为 UTF-8,但它不会告诉 Perl。 Perl 必须在应用字符正则表达式而不是字节正则表达式之前明确地被告知它正在读取 UTF-8。如果您自己从 STDIN 读取输入,则需要按照我的描述设置 binmode。如果您使用的是 CGI.pm,则需要在此处修改选项或在输入上使用 Encode::decode_utf8。如果您正在使用其他获取输入的方式,则需要了解这一点。
  • "使用 utf8;"只告诉源码is in UTF-8,与程序本身的实际运行无关。

标签: regex perl unicode internationalization


【解决方案1】:

问题很可能归结为没有

use utf8;

(或您使用的任何编码系统的等效项)在您的程序中。您那里的奇怪替换看起来像是按字节而不是按字符正则表达式替换的问题。

#!/usr/local/bin/perl
use warnings;
use strict;
use utf8;
binmode STDOUT, "utf8";
my $string = "été";

$string =~ s/[áàâã]/a/gi; #This line always prepends an "a"
$string =~ s/[éèêë]/e/gi;
$string =~ s/[úùûü]/u/gi;

print "$string\n";

打印

ete

如果您从文件或标准输入读取输入,请确保将流设置为 utf8 或任何适合编码的内容。对于STDIN 使用

binmode STDOUT, "utf8";

如果您正在从文件中读取,请使用

open my $file, "<:utf8", "file_name"

获得正确的编码。如果不是 UTF-8,请使用 encoding(name) 而不是 utf8

【讨论】:

  • 鉴于 Mike 有“使用 utf8;”在他的源代码中,Unicode 源代码将被很好地接受。这表明他的输入字符串没有被正确解释。请记住,utf8 pragma 影响程序代码而不是源代码。
  • 帖子中没有提到输入的来源。
  • 流来自 AJAX 请求。见编辑 2
【解决方案2】:

有些东西告诉我这是因为它不知道如何处理带有重音的字符。通过查看您的正则表达式,一切似乎都很好。您可能需要添加:

use utf8;

【讨论】:

    【解决方案3】:

    这可能是因为您使用的是 UTF8 字符串,并且它会将它们解析为不是或类似的。

    您应该使用类似 [áàâã] 的东西,而不是使用类似的东西 [\xE1-\xE5]

    也可能在你的代码中使用use utf8;

    【讨论】:

    • 一个或另一个就足够了。
    • 但同时使用两者并没有什么坏处:D
    【解决方案4】:

    这也可能是Unicode Normalisation 的问题,因为某些系统(我在看你,OS X)将扩展的 Latin1 字形表示为特定的规范化表示,当您专门引用一个字符时可能会破坏正则表达式使用 unicode 或 hex 表示。

    【讨论】:

    • 如果迈克有“使用utf8;”在他的程序中,这个问题将由 Perl 解决。
    【解决方案5】:

    但是你真的想要使用正则表达式吗?也许像Text::Unidecode 这样的东西会更好

    $ perl -Mutf8 -MText::Unidecode -E 'say unidecode("été")'
    ete
    

    【讨论】:

    • 请注意 utf8 pragma 的重要性。如果你的源代码中有 Unicode,你需要告诉 Perl。
    【解决方案6】:

    我怀疑正在发生的事情是正则表达式的 [áàâã] 部分实际上不是匹配字符,而是匹配字节。这些字符的 UTF-8 编码在正则表达式中看起来就像这样:

    [\xC3\xA1\xC3\xA0\xC3\xA2\xC3\xA3]
    

    因此,当输入正则表达式时,例如 'é' (\xC3\xA9),它一次查看一个字节,匹配 \xC3,并用 'a' 替换它。它对它可以找到的所有 \xC3 字节执行此操作。所以,'été' 变成了 'a\xA9ta\xA9'。

    然后是第二个正则表达式,如下所示:

    [\xc3\xA9\xC3\xA8\xC3\xAA\xC3\xAB]
    

    出现,它匹配 \xA9 部分,并用“e”替换它。所以现在,'a\xA9ta\xA9' 变成了 'aetae'。

    当您将 [áàâã] 替换为 (á|à|â|ã) 时,它会在第一遍中正确匹配完整字符,但随后您的第二个正则表达式出现原始问题,并且 \xC3 字符被替换为'e' 代替。

    如果这种情况仍然存在,即使使用use utf8;,那么Perl 正则表达式引擎中可能存在错误(或至少是一个限制)。

    【讨论】:

    • perl -v 返回:“这是 perl, v5.10.0 为 i586-linux-thread-multi 构建”
    【解决方案7】:

    我想说你不应该在这里使用正则表达式。实现这一点的最简单方法(尽管这可能是不可取的)是将您的输入字符串转换为 US ASCII。相应的转换表应该知道eé 最接近。

    另一种选择是使用 Unicode 并将您的字符串规范化为 NFD。这会将所有重音字母分解为基本字母+变音符号。然后你可以遍历你的字符串并删除所有组合的变音字符。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-28
      • 2015-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-19
      • 2021-02-22
      相关资源
      最近更新 更多