Perl正则表达式替换逻辑unicode字符

时间:2016-09-02 15:23:02

标签: perl unicode

这是一个简单的替换,它在unicode字符串中围绕大写字符添加括号。如您所见,结果相当丑陋:

~$ echo "Whatéver 5" | perl -ape "s/(\p{Upper})/(\1)/g"
(W)hat(�)�ver 5

我的理解是正则表达式操作的是“代码点”而不是“逻辑字符”,它将我的“é”分成无意义的字符。有没有办法强制正则表达式一次处理逻辑unicode字符?

谢谢,

3 个答案:

答案 0 :(得分:2)

你没有告诉Perl期望UTF-8输入,所以它将编码的每个字节视为一个单独的字符

在程序中,您可以设置三个标准IO通道的默认编码,如

use open ':std' => ':encoding(UTF-8)'

在命令行中,选项-CS执行相同的操作,因此这应该适合您。我删除了不必要的autosplit选项,并将\1替换为替换字符串中的正确$1

echo "Whatéver 5" | perl -CS -pe "s/(\p{Upper})/($1)/g"

答案 1 :(得分:2)

正如其他答案所示,在Perl中打开UTF-8是一个零碎的过程。语法和原始字符串有use utf8。然后你必须确保所有的文件句柄都是UTF-8。那么@ARGV呢? readdirglob``的输出?

没有什么比让一半程序使用ASCII而另一半使用UTF-8更糟糕了。 utf8::all救援!

安装它,添加use utf8::all,它将打开UTF-8 ...所有这些。别人想通了,你不用担心。

$ echo "Whatéver 5" | perl -ape "use utf8::all; s/(\p{Upper})/(\1)/g"
(W)hatéver 5

答案 2 :(得分:1)

假设您的终端使用UTF-8编码,

$ echo -n "é" | perl -ne 'printf "%vX\n", $_'

给出

C3.A9

所以Perl程序的输入内部没有转换为Unicode(它仍然是一个UTF-8字节的字符串)

要将输入转换为Perl字符串,请使用选项-CI在标准输入流上添加UTF-8图层:

$ echo -n "é" | perl -CI -ne 'printf "%vX\n", $_'

输出现在是

E9

但是,如果您还尝试将字符打印回标准输出 你不会从终端获得é,而是获得unicode替换字符。这是因为字符0xE9是Unicode,但终端期望UTF-8,0xE9无效UTF-8:

$ echo -n "é" | perl -CI -nE 'printf "$_: %vX\n", $_, $_'
�: E9

要获得正确的输出,您还可以在标准输出流上添加UFT-8编码图层(使用-CO标志):

$ echo -n "é" | perl -CIO -nE 'printf "$_: %vX\n", $_, $_'
é: E9

根据perlunicode

  

“Upper”是“大写”的同义词,我们可以写   \ p {大写}等价于\ p {Upper}

  

例如,\ p {Uppercase}匹配任何单个字符   Unicode“大写”属性

如果您尝试在字节字符串上使用\p{Upper},您将不会收到来自Perl的任何警告。 0xC00xDE范围内的字节也将匹配大写属性。尝试

perl -E 'for $i (0x80..0xFF) {$_=chr $i; printf "%x\n", $i if /\p{Upper}/}'

这解释了你得到的输出:

$ echo "Whatéver 5" | perl -ape "s/(\p{Upper})/(\1)/g"
(W)hat(�)�ver 5

此处,字母é表示为2个字节(UTF-8)0xC30xA90xC3将与Unicode Upper匹配属性。

因此,您的问题的解决方案是在标准输入和输出上添加UTF-8编码图层(您可以使用-CI合并-CO-CS):

echo "Whatéver 5" | perl -CS -ape "s/(\p{Upper})/(\1)/g"

带输出:

(W)hatéver 5