用于从文件中删除UTF-8字符范围的脚本

时间:2017-02-07 16:17:01

标签: python perl unicode utf-8

我的问题是我有一个包含UTF-8的数据文件,其中大部分都是有效的,必须保留,但其中一些有随机的"垃圾" UTF-8,即0xf0 - 0xff范围内。可以在下面看到坏数据的十六进制示例

 f4 80 80  ab f4 80 80 b6 f4 80 80 
 a5 f4 80 80 a6 f4 80 80  83 f4 80 80 b6 f4 80 81  
 84 f4 80 81 98 f4 80 81  87 f4 80 81 8c f4

我正在尝试编写一个perl脚本,用于搜索和替换第一个字节在0xf0 - 0xff范围内的字符。在this website上,代码页被列为私人使用。

我现有的尝试要么什么都不做,要么只能删除多字节字符的第一个字节,例如perl -CSD -pi.orig -e 's/[\x{f4}-\x{ff}]/?/g'运行perl v5.12.5

我不是一个perl专家,也不是一个utf-8专家。我也可以在ruby / python / C ++(98)/中做这件事,只要它在linux盒子上相对便携。

这是指向垃圾数据片段的链接。 http://pastebin.com/LR0StPHu

3 个答案:

答案 0 :(得分:5)

好的,不要混淆一些事情。

第一个字节为0xf0的UTF-8字符长度为四个字节,这是您编写合法Unicode字符所需的最多字符。由于超过94%的可能Unicode范围要求第四个字节,0xf0不会映射到任何单个代码页,当然也不会映射到私人使用区域。

此类字符在<{3}}之外。但这与无效或私人使用不同;它只是意味着他们的代码点大于U + FFFF(十进制值65,535)。

如果要排除BMP之外的所有字符,您应该搜索与此正则表达式匹配的字符:

[\x{10000}-\x{10FFFF}]

使用Perl的\x{ ... }插值语法,以十六进制代码点值包含字符。如果您实际使用Perl,那么为了便于使用,您可能希望将正则表达式放入变量中(使用quote-regex构造qr( ... ),因为裸斜杠将立即尝试在分配时将正则表达式与$_匹配):

my $not_bmp = qr([\x{10000}-\x{10FFFF}]);

但是,再次删除匹配该正则表达式的字符可以消除94%以上的可能Unicode字符,因此请确保这是您想要的。

如果你真的只想消除私人使用角色 - 其中一些是里面的 BMP - 只要专门排除那些范围。使用Perl或Python或任何其他支持UTF-8的语言,您不必担心字节;只需检查代码点。

正如Basic Multilingual Plane将告诉您的那样,三个私人使用区域在这些代码点范围内:

  • U + E000..U + F8FF
  • U + F0000..U + FFFFF
  • U + 100000..U + 10FFFF

所以相应的Perl正则表达式如下所示:

my $pua = qr([\x{e000}-\x{f8ff}\x{f0000}-\x{fffff}\x{100000}-\x{10ffff}]);

许多其他语言具有类似的Unicode支持(与UTF-8字符匹配,包括按代码点的字符串中的字符,依此类推)。例如,这里的Ruby,主要区别在于使用\u{...}而不是\x{...}进行插值:

not_bmp = %r([\u{10000}-\u{10FFFF}])
pua = %r([\u{e000}-\u{f8ff}\u{f0000}-\u{fffff}\u{100000}-\u{10ffff}])

Python \u转义只能使用四个十六进制数字,但是如果你有Python3 - 或者在 wide 模式下编译的Python2 - 你可以使用大写\U,正好是8个(通过{ ... }没有可变长度支持,如Perl和Ruby所有):

not_bmp = re.compile(u'[\U00010000-\U0010ffff]')
pua = re.compile(u'[\ue000-\uf8ff\U000f0000-\U000fffff\U00100000-\U0010ffff]')

答案 1 :(得分:3)

您需要处理字符,而不是字节。

如果您的代码中包含数据,并且使用use utf8编译指示告诉Perl您的程序的源代码是在utf8中。我们为示例执行此操作,以便您可以复制/粘贴我的代码。

您可以使用字符类[]中的\x{} escape sequence进行字符串替换。这些可以在范围内使用,也可以单独使用。

use utf8;

my $foo = "asfd ☃  Բարեւ ສະບາຍດີ";
$foo =~ s/[\x{10002b}\x{100036}]//g;
CORE::say $foo;

这将输出:

asfd ☃  Բարեւ ສະບາຍດີ

(打印警告中还有宽字符,但是请忽略它,因为我的STDOUT未正确打开)。

我替换的两个字符\x{10002b}\x{100036}是示例数据中的前两个字符。我在IDE中使用的字体显示了字符的序号,它没有任何字形,所以我很容易告诉这些字符是什么。

my font shows character ordinals

这些字符来自Supplementary Private Use Area-B。 (Wikipedia

  

16 PUA-B U + 100000..U + 10FFFF补充私人使用区-B 65,536 65,534未知

所以我们也可以做范围。

my $foo = "asfd ☃  Բարեւ ສະບາຍດີ";
$foo =~ s/[\x{100000}-\x{10ffff}]//g;
CORE::say $foo;

输出:

asfd ☃  Բարեւ ສະບາຍດີ

要获取所有私人使用区域,您需要包含列出here的三个范围。

/[\x{E000}-\x{F8FF}\x{F0_000}-\x{FF_FFD}\x{100_000}-\x{10f_fff}]//g;

答案 2 :(得分:1)

浪费您的时间来查找私人使用区域的十六进制范围。简单地说

S / \ p {Private_Use} //克

perluniprops是pod文件,它提供了所有Unicode属性。如果你只想要上面的BMP私人使用区域,你可以咨询它(grepping for Private)以找到如何匹配它们。