如何在命令行上处理UTF8编码(使用Perl或Python)?

17

我该如何在命令行上使用Perl(或Python)处理utf8编码的文本?

我想要把每个单词中的字符分开,例如。这对于非utf8文本非常容易,例如:

$ echo "abc def" | perl -ne 'my @letters = m/(.)/g; print "@letters\n"' | less
a b c   d e f

但使用utf8,它当然不起作用:

$ echo "одобрение за" | perl -ne 'my @letters = m/(.)/g; print "@letters\n"' | less
<D0> <BE> <D0> <B4> <D0> <BE> <D0> <B1> <D1> <80> <D0> <B5> <D0> <BD> <D0> <B8> <D0> <B5>   <D0> <B7> <D0> <B0>
因为它不知道二字节字符的存在。
同时了解一下Python如何进行utf8的命令行处理也很重要。

使用以下命令将“одобрение за”转换为每个字符后跟一个空格的形式: $ sed 's/./& /g' <<< "одобрение за" 结果为: о д о б р е н и е з а - Ignacio Vazquez-Abrams
1
@Ignacio Vazquez-Abrams:sed 's/./& /g' 对于字形不起作用(如果文本包含组合字符,例如 "Солжени́цын",则很重要)。在Perl、Python中,可以使用 /\X/ 正则表达式来解决这个问题。 - jfs
5个回答

28

"-C"标志控制一些Perl Unicode功能(请参见perldoc perlrun):

$ echo "одобрение за" | perl -C -pe 's/.\K/ /g'
о д о б р е н и е   з а 

要指定标准输入/输出所使用的编码,您可以使用PYTHONIOENCODING环境变量:

$ echo "одобрение за" | PYTHONIOENCODING=utf-8 python -c'import sys
for line in sys.stdin:
    print " ".join(line.decode(sys.stdin.encoding)),
'
о д о б р е н и е   з а 
如果您想将文本按字符(图形)边界拆分(而不是像上面的代码按代码点),那么您可以使用/\X/正则表达式:
$ echo "одобрение за" | perl -C -pe 's/\X\K/ /g'
о д о б р е н и е   з а 

请参见字形簇边界(Grapheme Cluster Boundaries)

在Python中,\Xregex模块支持。



6

我想:“在Perl中这有多难呢?”

结果证明,这非常容易。不幸的是,花费的时间比我预计的要长。

快速浏览一下use utf8,我发现它已经过时了。Perl的binmode看起来很有前途,但还不够。

发现有一个Perluniintro,将我引向Perlunicode,其中说我应该查看Perlrun。然后,我找到了我要找的东西。

Perl有一个命令行开关-C,可以将Perl切换到Unicode。但是,-C命令行开关也需要一些选项。您需要指定Unicode的内容。这里有一个方便的表格,显示各种选项。似乎perl -C本身就可以。这结合了各种选项,相当于-CSDL-C255。但是,这意味着如果您的LOCALE未设置为Unicode,则Perl无法工作。

相反,您应该使用perl -CSD-perl -C63

$ echo "одобрение за" | perl -CSD -ne 'my @letters = m/(.)/g; print "@letters\n"'
о д о б р е н и е   з а

是的,那很有效。

回答问题可以学到很多知识。


1
+1:你可能是指 -CSDA(用于处理 @ARGV),尽管从 OP 中可以假定区域设置基于 utf-8,因此仅使用 -C 就足够了。 - jfs
2
使用 utf8 并不完全过时,它只有一个有限的目的,就是告诉 Perl 源代码采用 utf8 编码。你需要做其他事情来处理 utf8 编码的数据输入和输出。 - Alex
3
好的,utf8编译指示符最开始的计划比实际结果要雄心勃勃得多。它最初被设想为更像utf8::all的东西。 - brian d foy
@jfs -C 不足以处理 @ARGV 作为 UTF-8 编码的字符串,即使在 UTF-8 区域设置下也是如此,因为 -C 暗示了 -CSDL。它并不暗示 A。这在我的回答中有所体现。 - Robin A. Meade
1
@RobinA.Meade:是的,在某些情况下,-C 可能不够用。从你的回答 -C vs. -CA 看来:perl -C -E 'say length("$_") foreach @ARGV' '' -> 4。而 perl -CA -E 'say length("$_") foreach @ARGV' '' -> 1 - jfs

4
我不会Perl,所以我会用Python回答。
Python并不知道输入的文本是Unicode编码。您需要显式地将其从UTF-8或实际使用的任何编码解码为Unicode。然后,您可以使用正常的Python文本处理工具来处理它。
这里有一个简单的Python 2.x程序供您尝试:
```python # -*- coding: utf-8 -*- input_text = "你好世界" unicode_text = input_text.decode('utf-8') print(unicode_text) ```
请参考http://docs.python.org/howto/unicode.html
import sys

for line in sys.stdin:
    u_line = unicode(line, encoding="utf-8")
    for ch in u_line:
        print ch, # print each character with a space after

这个程序将从标准输入中复制行,并将每行转换为Unicode。编码格式指定为UTF-8。然后,for ch in u_line将把ch设置为每个字符。接着,print ch,是Python 2.x中打印一个字符的简单方法,其后跟一个空格,没有回车。最后,裸的print添加了一个回车符。
我仍然在大部分工作中使用Python 2.x,但对于Unicode,我建议你使用Python 3.x。Unicode处理真的得到了很大的改进。
这是上述程序的Python 3版本,在我的Linux计算机上测试过。
import sys

assert(sys.stdin.encoding == 'UTF-8')
for line in sys.stdin:
    for ch in line:
        print(ch, end=' ') # print each character with a space after

默认情况下,Python 3 假定输入的编码为 UTF-8。然后,Python 会将其解码为 Unicode。Python 3 中的字符串始终是 Unicode;有一种特殊类型的 bytes() 用于包含非 Unicode 值(“字节”)的类似字符串的对象。这与 Python 2.x 相反;在 Python 2.x 中,基本字符串类型是字节串,而 Unicode 字符串是一个特殊的新事物。
当然,断言编码为 UTF-8 并不是必需的,但这是一种很好的简单方式来记录我们的意图,并确保默认设置没有被改变。
在 Python 3 中,print() 现在是一个函数。不再需要使用在 print 语句后添加逗号的奇怪语法来使其打印空格而不是换行符,现在有一个命名关键字参数可以让你更改结束字符。
注意:最初我在 Python 2.x 程序中处理输入行后使用了裸的 print 语句,在 Python 3.x 程序中使用了 print()。正如 J.F. Sebastian 指出的那样,代码正在打印输入行的字符,最后一个字符将是换行符,因此实际上并不需要额外的打印语句。

Python 3.x 的 Unicode 相关内容并没有太多变化。只有默认编码和代码本身中的文字字面量发生了改变。此外,一些东西已经被重命名。在这方面没有添加任何新功能。 - nosklo
@nosklo,就像我的第二个示例所示,Python 3.x 中的默认设置现在支持 Unicode。无需显式将输入字符串转换为Unicode字符串,您可以直接处理它。这是我认为非常重要的一项变化。 - steveha
已经有一个换行符了,你不需要一个单独的 print 语句,例如 print "\n", 可以只打印出换行符。 - jfs
@J.F. Sebastian,你说得对。我会缩短示例。 - steveha
也就是说:1. 即使 sys.stdout.encoding 正确,print unicode_string 的输出结果仍然不符合预期(这是 Python 的一个 bug),因此在打印之前需要将其编码为字节或使用 PYTHONIOENCODING。2. sys.stdin.encoding 取决于本地语言环境,因此断言可能会失败。 - jfs

4
$ echo "одобрение за" | python -c 'import sys, codecs ; x = codecs.
getreader("utf-8")(sys.stdin); print u", ".join(x.read().strip())'
о, д, о, б, р, е, н, и, е,  , з, а

或者如果您需要Unicode编码点:

$ echo "одобрение за" | python -c 'import sys, codecs ; x = codecs.
getreader("utf-8")(sys.stdin); print u", ".join("<%04x>" % ord(ch) 
for ch in x.read().strip())'
<043e>, <0434>, <043e>, <0431>, <0440>, <0435>, <043d>, <0438>, 
<0435>, <0020>, <0437>, <0430> 

1
如果标准输出被重定向,例如 python -c... | cat,这将无法工作。 - jfs

1
要在Perl中处理命令行上的UTF-8,我们必须考虑STDIN、STDOUT、STDERR、参数以及源代码(作为-e-E选项的参数给出)。
考虑以下测试用例:
echo -n "одобрение за"  | perl -Mstrict -w -E '
  while (<STDIN>){ s/\X\K/ /g; say; }
  say "Arguments and their length:";
  say "  $_\t", length("$_") foreach @ARGV;
  say "Length of  in the source code is ", length("");
' a 

这是一个很好的测试用例,因为它有3个地方包含UTF-8编码字符:

  1. 在标准输入上,
  2. 作为参数,以及
  3. 在源代码中(作为传递给-E选项的参数)。

(顺便说一下,我的终端处于UTF-8区域设置。)

结果:

� � � � � � � � � � � � � � � � � �   � � � � 
Arguments and their length:
 a  1
  4
Length of  in the source code is 4

首先,让我们摆脱问号。告诉 Perl 标准流是 UTF-8 编码的字符。为此,请添加 -CSD

echo -n "одобрение за"  | perl -Mstrict -w -CSD -E '
  while (<STDIN>){ s/\X\K/ /g; say; }
  say "Arguments and their length:";
  say "  $_\t", length("$_") foreach @ARGV;
  say "Length of  in the source code is ", length("");
' a 
注意:我本可以简单地使用-C,因为-C意味着-CSDL,在UTF-8语言环境下,这与-CSD相同,如perlrun所解释的那样。
о д о б р е н и е   з а 
Arguments and their length:
  a 1
  ð 4
Length of ð in the source code is 4

很好,这样就解决了问号的问题。

但现在参数和源代码中的表情符号已经混乱了。

我们必须告诉Perl我们的参数是UTF-8编码。我们可以通过将-CSD更改为-CSDA来实现:

echo -n "одобрение за"  | perl -Mstrict -w -CSDA -E '
  while (<STDIN>){ s/\X\K/ /g; say; }
  say "Arguments and their length:";
  say "  $_\t", length("$_") foreach @ARGV;
  say "Length of  in the source code is ", length("");
' a 

结果:

о д о б р е н и е   з а 
Arguments and their length:
 a  1
  1
Length of ð in the source code is 4

很好。表情符号参数已经被修复,其长度为1个字符,如预期所述。

源代码中的表情符号仍然存在问题。

要告知perl源代码已编码为UTF-8,请将use utf8;添加到源代码或-Mutf8添加到命令行选项中:

echo -n "одобрение за"  | perl -Mutf8 -Mstrict -w -CSDA -E '
  while (<STDIN>){ s/\X\K/ /g; say; }
  say "Arguments and their length:";
  say "  $_\t", length("$_") foreach @ARGV;
  say "Length of  in the source code is ", length("");
' a 

结果:

о д о б р е н и е   з а 
Arguments and their length:
 a  1
  1
Length of  in the source code is 1

好的,现在我们得到了源代码中表情符号字符的预期结果。

总结:

  • 添加-CSD以告诉perl标准流是UTF-8编码的。
  • 将其更改为-CSDA以处理UTF-8编码的参数。
  • 在源代码中添加use utf8;或添加-Mutf8选项以告诉perl源代码是UTF-8编码的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接