将<U9999>格式的Unicode字符转换为ASCII等价物的脚本
我正在修改Linux的区域设置文件 /usr/share/i18n/locales
(比如pt_BR),目的是改变日期、时间、数字等的默认格式。不过,由于unicode字符以 <U9999>
的格式显示,导致文本很难阅读。
这里有一段示例代码:
LC_TIME
abday "<U0044><U006F><U006D>";"<U0053><U0065><U0067>";/
"<U0054><U0065><U0072>";"<U0051><U0075><U0061>";/
"<U0051><U0075><U0069>";"<U0053><U0065><U0078>";/
"<U0053><U00E1><U0062>"
那么,怎么写一个简单的脚本(可以是bash、python、perl等)来把这些文本中的 <Uxxxx>
代码替换成它们的ASCII等价字符呢?(没错,它们都是ASCII字符,值在255以下,大部分甚至在127以下)
如果有多个答案,我会选择最优雅或者解释得最详细的那个(比如命令中使用的选项和标志)
举个例子,上面的文本可以转换成:
LC_TIME
abday "Dom";"Seg";/
"Ter";"Qua";/
"Qui";"Sex";/
"Sáb"
如果能再提供一个脚本,能够反向操作:把给定字符串中的所有字符转换成 <Uxxx>
格式,那就更好了。
谢谢!
2 个回答
这里有一个用Python写的脚本,可以把 <U9999>
这样的字符串转换成它们对应的ASCII(0-127)字符,使用的是 unidecode
模块:
#!/usr/bin/env python
import fileinput, re, sys
from unidecode import unidecode # to install, run: $ pip install unidecode
for line in fileinput.input(inplace='--inplace' in sys.argv):
print re.sub(r'<U([0-9A-F]{4})>',
lambda m: unidecode(unichr(int(m.group(1), 16))),
line),
这个脚本可以从标准输入(stdin)接收数据,也可以从命令行中指定的文件读取。
$ u9999-to-ascii data.in
LC_TIME
abday "Dom";"Seg";/
"Ter";"Qua";/
"Qui";"Sex";/
"Sab"
需要注意的是,ASCII不支持 á
这个字符,所以脚本会把它替换成ASCII中相似的字符 a
。
如果你不需要ASCII格式的话:
#!/usr/bin/env python
from __future__ import print_function
import fileinput, re, sys
for line in fileinput.input(mode='rb', inplace='--inplace' in sys.argv):
print(re.sub(br'<U([0-9A-F]{4})>', lambda m: br'\u'+m.group(1),
line).decode('raw-unicode-escape'), end='')
这个脚本在Python2.6及以上版本和Python3.x中都能运行。举个例子:
$ u9999-to-unicode.py data.in
LC_TIME
abday "Dom";"Seg";/
"Ter";"Qua";/
"Qui";"Sex";/
"Sáb"
注意,这里有 á
。如果你的终端编码不支持 data.in
中的所有Unicode字符,这个脚本可能会出错。在这种情况下,你可以使用 .encode()
方法。
使用字段
#!/bin/bash
awk -F'<U0+|>' '{
for(i=1;i<=NF;i++)
if($i ~ "^[0-9A-F]+$")
$i=sprintf("%c", strtonum("0x"$i))
}1' OFS="" /path/to/infile
解释
-F'<U0+|>'
: 这是让这个脚本变得简短的关键。我们告诉awk,字段的分隔符可以是<U0+
或者简单的>
。这样做的好处是,awk会自动去掉这些字符,所以在进行数字转换时,我们就不需要手动用gsub()
来处理了。for(i=1;i<=NF;i++)
: 遍历每一个字段if($i ~ "^[0-9A-F]+$")
: 检查当前字段是否只由十六进制数字组成。记住,由于第一点的原因,像<U006F>
现在会被看作是6F
。$i=sprintf("%c", strtonum("0x"$i))
: 用对应的ASCII值替换十六进制数字。我们必须在字段$i
前面加上"0x"
,这样awk才能知道这是一个十六进制值。}1
: 这是一个简写,表示必须要有print
,也就是总是打印每一行。OFS=""
: 设置输出字段分隔符为空字符串。如果不这样做,输出中会在每个<U0+
或>
的地方出现空格。
使用 match() [需要 gawk]
#!/bin/bash
gawk '{
while(match($0, /<U[0-9A-F]+>/)){
pat = substr($0,RSTART,RLENGTH)
gsub(/U0+|[<>]/,"",pat)
asc = sprintf("%c", strtonum("0x"pat))
$0 = substr($0, 1, RSTART-1) asc substr($0, RSTART+RLENGTH)
}
}1' /path/to/infile