如何在 perl 正则表达式替换命令中使用 unicode 字符?
Posted
技术标签:
【中文标题】如何在 perl 正则表达式替换命令中使用 unicode 字符?【英文标题】:How can I use unicode characters in perl regex substitution command? 【发布时间】:2022-01-18 07:43:04 【问题描述】:这在使用 unicode 字符时不起作用(在 Ubuntu bash 中):
$ perl -pC -e's/[à]/a/gu' <<< 'à'
à
$ perl -pC -e's/[b]/a/gu' <<< 'b'
a
即使 PCRE 似乎支持它(至少根据regex101)。
我做错了什么?我在 perl 命令中缺少一些标志吗?
这在 javascript 中“正常工作”,所以如果我能在命令行中为此提供一个简单的单行器,我会使用 node ......但我仍然想知道为什么 perl 命令不起作用。
对于上下文:
我正在尝试使用 /[àâáãä]/a/g
、/[òôóõö]/o/g
等替换来关联字典文件(即删除单词列表的重音等),因此我可以使用它来进行拼写检查重音-不敏感(例如在 IntelliJ Idea 中)。
基本上这些是制作“asciified”额外字典的步骤:
-
下载该语言的 .dic 文件(所有单词的列表)
使用 grep 过滤包含非 ascii/可替换字符的单词
连续使用正则表达式替换以使单词不区分重音
在 IDE 中导入 asciified .dic 文件(标准语言词典除外)
【问题讨论】:
我无法重现,它在新启动的 Ubuntu Docker 映像中对我来说工作正常(尽管我必须bind 'set input-meta on'; bind 'set output-meta on'
在 Bash 中启用 8 位 I/O)。
【参考方案1】:
一种实用的方法是使用Text::Unidecode
perl -C -MText::Unidecode -pe'unidecode($_)' <<< 'à'
打印a
。该模块将 Unicode 文本转译为纯 ASCII。
另一种方法:使用Unicode::Normalize 分解字符(“规范化”),使字符及其diacritical marks(组合重音符号)分离成它们自己的代码点,同时它们仍然形成有效的grapheme,然后使用简单的正则表达式删除变音符号(\pNonspacingMark
或 \pMn
)。
这两种方式都会有例外和极端情况,但我认为它可能只是满足您的需要。
对于包含特定(文字)字符的代码,需要通过utf8 pragma 和use utf8;
或命令行标志-Mutf8
告诉Perl 程序源是UTF-8
perl -C -Mutf8 -pe's/[à]/a/g' <<< 'à'
【讨论】:
在编写 Programming Perl,第 4 版时,@tchrist 向我指出了一些 Mac OS X 问题,我们正在获取 NFD 输入并期待 NFC,或者可能反过来。操作系统界面很难,我想他可能给我发了用加泰罗尼亚语编写的示例程序。美好时光 :) 所以,是的,始终按照您想要使用的内容进行规范化,并且可能在退出的过程中重新规范化。 不敢相信我花了这么长时间才注意到 unidecode 中的“de”... oof - 我很困惑为什么制表符完成在 apt 中不起作用。无论如何,对于遇到此问题的任何人:该模块可以使用sudo apt install libtext-unidecode-perl
安装在 Ubuntu 中。【参考方案2】:
您需要添加-Mutf8
来告诉 Perl 程序是使用 UTF-8 而不是 ASCII 编码的。
$ perl -pC -Mutf8 -e's/[à]/a/gu' <<< 'à'
a
【讨论】:
-Mutf8
是我想要的。谢谢! +1【参考方案3】:
简短的回答是将-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
您可以使用perlstring()
来确保 Perl 正在接收您认为的输入。
$ perl -p -MB -E 'say B::perlstring($_)' <<< 'à'
"\303\240\n"
à
$ perl -pC -MB -E 'say B::perlstring($_)' <<< 'à'
"\xe0\n"
à
你可以看到,没有-C
,Perl 正在接收 2 个分解的字符。
根据具体情况,Perl 将字符转储为八进制代码 (\340
) 或十六进制代码 (\xE0
)。请注意,您始终可以将命令行中的原始 unicode 字符替换为转义码版本。这是一个很好的方式来明确什么否则会模棱两可。
$ perl -pC -e 's/[\xE0]/a/gu' <<< 'à'
a
如果您不想记住 UTF8 模式,可以将这些选项放入 PERL5OPT
环境变量中或创建一个 shell 别名。小心把它变成全球性的!
$ 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($_)' <<< 'à'
"\xe0\n"
à
或作为 shell 别名。
alias uperl='perl -C -Mutf8'
有关如何使用 Swiss Army 电锯命令行的更多信息,请参阅 perlrun。
另见B::Deparse。
【讨论】:
【参考方案4】:以下是我实施第 2 步和第 3 步的方法。 这可以在例如these 字典中使用(尽管我没有在每种语言上都对其进行测试)。
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
【讨论】:
不错。 (可以先过滤掉 cmets,让其他工具不用担心;然后也可以通过uniq
管道删除重复项)
这里的改进是删除原始字典中存在的重复项 - 即当 asciified 单词最终与原始字典中的正常单词相同时。但是做那个优化涉及更多的复杂性,我现在对这个解决方案很满意。除了单行代码之外,我不想过多地研究 perl 代码哈哈
最后的uniq
(或你的awk
)不会这样做吗? (我看到我搞砸了——你想跳过处理 cmets 而是留下它们,而不是删除它们。)顺便说一句,我并不是要暗示更多 Perl :))
@zdim awk
仅从生成的 dic 中删除重复项。但是,unidecode 生成的单词可能与原始 dic 中的单词相同 - 即使它们不是必需的,这些单词也不会被删除,因为这暗示原始 dic 也在使用中。例如,在葡萄牙语中,有“maçã”和“maca”——这两个词都是有效的。该算法将“maca”生成为“maçã”的asciified版本,但添加一个已经存在的单词是一种“浪费”。这可能看起来很小,但由于动词变位,可能会有很多这样的词,例如。 “cantara”和“cantará”。
这种优化不会做任何特别的事情,只是减少 dic 文件的大小(可能会减少相当大的数量,具体取决于语言)。由于单线可能不可行,我认为这可能不值得麻烦:)以上是关于如何在 perl 正则表达式替换命令中使用 unicode 字符?的主要内容,如果未能解决你的问题,请参考以下文章