如何在 perl 正则表达式替换命令中使用 unicode 字符?

How can I use unicode characters in perl regex substitution command?

提问人:geekley 提问时间:12/15/2021 最后编辑:lordadmirageekley 更新时间:10/1/2022 访问量:527

问:

这在使用 unicode 字符时不起作用(在 Ubuntu bash 中):

$ perl -pC -e's/[à]/a/gu' <<< 'à'
à
$ perl -pC -e's/[b]/a/gu' <<< 'b'
a

尽管它似乎受到 PCRE 的支持(至少根据 regex101)。

我做错了什么?我在 perl 命令中是否缺少一些标志?

这在 javascript 中“有效”,所以如果我能在命令行中想出一个简单的单行代码,我会使用 node ......但我仍然想知道为什么 perl 命令不起作用。


对于上下文:

我正在尝试使用像 、 等替换来发音字典文件(即删除单词列表的重音等),这样我就可以用它来使拼写检查重音不敏感(例如在 IntelliJ Idea 中)。/[àâáãä]/a/g/[òôóõö]/o/g

基本上,这些是制作“asciified”额外词典的步骤:

  1. 下载该语言的 .dic 文件(所有单词的列表)
  2. 使用 grep 过滤包含非 ASCII / 可替换字符的单词
  3. 连续使用正则表达式替换使单词不区分重音
  4. 在 IDE 中导入 asciified 的 .dic 文件(除了标准语言词典之外)
正则表达式 perl unicode 命令行 UTF-8

评论

0赞 tripleee 12/15/2021
我无法复制,它在新启动的 Ubuntu Docker 映像中对我来说工作正常(尽管我必须在 Bash 中启用 8 位 I/O)。bind 'set input-meta on'; bind 'set output-meta on'

答:

8赞 zdim 12/15/2021 #1

所有这些的一种实用方法是使用 Text::Unidecode

perl -C -MText::Unidecode -pe'unidecode($_)'  <<< 'à'

指纹。该模块将 Unicode 文本音译为纯 ASCII。a

另一种方法:使用 Unicode::Normalize 分解字符(“normalize”),以便将字符及其变音符号(组合重音)分隔成它们自己的代码点,同时它们仍然形成一个有效的字素,然后用简单的正则表达式删除变音符号(或)。\p{NonspacingMark}\p{Mn}

这两种方式都会有例外和边缘情况,但我认为它可能只是您需要的。


对于包含特定(文字)字符的代码,需要通过带有命令行标志或带有命令行标志的 utf8 编译指示告诉 Perl 程序源代码是 UTF-8use utf8;-Mutf8

perl -C -Mutf8 -pe's/[à]/a/g' <<< 'à'

评论

0赞 brian d foy 12/15/2021
在编写 Programming Perl, 4th Edition 时,@tchrist向我指出了一些 Mac OS X 问题,我们获得了 NFD 输入并期待 NFC,或者相反。操作系统接口很棘手,我想他可能已经给我发了用加泰罗尼亚语编写的示例程序。美好时光:)所以,是的,始终规范化为您想要使用的内容,并可能在返回途中重新规范化。
1赞 geekley 12/17/2021
简直不敢相信我花了那么长时间才注意到 unidecode 中的“de”......oof - 我很困惑为什么 tab 完成在 apt 中不起作用。无论如何,对于遇到此问题的任何人:该模块可以安装在 Ubuntu 中。sudo apt install libtext-unidecode-perl
4赞 BarneySchmale 12/15/2021 #2

您需要添加以告诉 Perl 程序是使用 UTF-8 而不是 ASCII 编码的。-Mutf8

$ perl -pC -Mutf8 -e's/[à]/a/gu' <<< 'à'
a

评论

0赞 geekley 12/17/2021
-Mutf8是我一直在寻找的。谢谢!+1
1赞 geekley 12/17/2021 #3

以下是我如何实现步骤 2 和 3。
例如,可以在这些词典中使用它(尽管我没有在每种语言上都对其进行测试)。

asciify-dic

#!/usr/bin/env bash
#License: "Zero-Clause BSD" <https://opensource.org/licenses/0BSD>
if [[ "$1" == "--help" ]]; then
  echo "Usage: $(basename "$0") INPUT_FILE > OUTPUT_FILE"
  echo "Asciify a .dic file (list of dictionary words)."
  echo ""
  echo "Generates a file with ASCII-only versions of the words that have non-ASCII chars."
  echo "These additional words can be used to make spell-checking accent-insensitive."
  echo "Comment lines beginning with % are left unchanged."
  exit
fi
# Filter words containing non-ascii characters, except in comments
grep -P '^\%|[^\x00-\x7F]' $1 |
# Make words accent-insensitive, except in comments
perl -C -MText::Unidecode -pe'next if /^\s*%/;unidecode($_)' |
# Remove duplicate lines, except in comments
awk '/^\s*%/||!seen[$0]++'

用法示例:

asciify-dic $DIC_NAME.dic > $DIC_NAME-asciified.dic

评论

1赞 zdim 12/18/2021
好。(可以先过滤掉注释,这样其他工具就不用担心了;然后也可以通过管道删除重复项uniq)
0赞 geekley 12/18/2021
这里的改进是删除原始 dic 中存在的重复项 - 即当发音的单词最终与原始词典中的普通单词相同时。但是进行优化涉及更多复杂性,我现在对这个解决方案感到满意。我不想过多地讨论单行之外的perl代码,哈哈
0赞 zdim 12/18/2021
决赛(或你的)不会这样做吗?(我看到我搞砸了——你想跳过处理评论,留下它们,而不是删除它们。顺便说一句,我并不是要暗示更多的Perl:))uniqawk
0赞 geekley 12/18/2021
@zdim 这只会从生成的 dic 中删除重复项。但是,unidecode 生成的单词可能与原始 dic 中的单词相同 - 即使它们不是必需的,也不会删除这些单词,因为这意味着原始 dic 也被使用。例如,在葡萄牙语中,有“maçã”和“maca”——这两个词都是有效的词。该算法将“maca”生成为“maçã”的缩写版本,但添加一个已经存在的单词是一种“浪费”。这可能看起来很小,但由于动词变位,可以有很多这样的词,例如“cantara”和“cantará”。awk
0赞 geekley 12/18/2021
这种优化不会做任何特别的事情,只是减小了 dic 文件的大小(可能有点大,取决于语言)。由于单行可能无法做到,我认为这可能不值得麻烦:)
2赞 lordadmira 12/17/2021 #4

简短的回答是添加到您的命令行。-Mutf8

如果你不确定Perl是如何解释你在命令行上写的内容的,你可以让它用核心函数把它吐回给你,或者用 .这将很快说明您的问题。(将“à”字符括在括号中在这里没有任何作用。B::perlstring()B::Deparse

$ perl -MO=Deparse -pC -e 's/à/a/gu' <<< 'à'

LINE: while (defined($_ = <ARGV>)) {
    s/\303\240/a/gu;
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK

看看你的替换是如何有 2 个字符的?

然后,您可以立即看到如何解决您的问题。use utf8

$ perl -MO=Deparse -Mutf8 -pC -e 's/à/a/gu' <<< 'à'
use utf8;
LINE: while (defined($_ = <ARGV>)) {
    s/\340/a/gu;
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK

你可以用它来确保Perl正在接收你认为的输入。perlstring()

$ perl -p -MB -E 'say B::perlstring($_)' <<< 'à'
"\303\240\n"
à
$ perl -pC -MB -E 'say B::perlstring($_)' <<< 'à'
"\x{e0}\n"
à

您可以看到,如果没有 Perl,则接收 2 个分解字符。-C

根据具体情况,Perl 将字符转储为八进制代码 () 或十六进制代码 ()。请注意,您始终可以将命令行中的原始 unicode 字符替换为转义码版本。这是一个很好的方法,可以明确说明否则会模棱两可的内容。\340\xE0

$ perl -pC -e 's/[\xE0]/a/gu' <<< 'à'
a

如果您不想记住 UTF8 模式,则可以将这些选项推入环境变量或创建 shell 别名。谨防将其全球化!PERL5OPT

$ export PERL5OPT='-C -Mutf8'
$ perl -MO=Deparse -p -e 's/à/a/gu' <<< 'à'
use utf8;
LINE: while (defined($_ = <ARGV>)) {
    s/\340/a/gu;
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK

$ perl -MB -p -E 'say B::perlstring($_)' <<< 'à'
"\x{e0}\n"
à

或作为 shell 别名。

alias uperl='perl -C -Mutf8'

有关如何将 Swiss Army Chainsaw 命令行的更多信息,请参见 perlrun

另请参见 B::D eparse