将<U9999>格式的Unicode字符转换为ASCII等价物的脚本

2 投票
2 回答
1921 浏览
提问于 2025-04-16 14:58

我正在修改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 个回答

1

这里有一个用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() 方法。

2

使用字段

#!/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

解释

  1. -F'<U0+|>': 这是让这个脚本变得简短的关键。我们告诉awk,字段的分隔符可以是 <U0+ 或者简单的 >。这样做的好处是,awk会自动去掉这些字符,所以在进行数字转换时,我们就不需要手动用 gsub() 来处理了。

  2. for(i=1;i<=NF;i++): 遍历每一个字段

  3. if($i ~ "^[0-9A-F]+$"): 检查当前字段是否只由十六进制数字组成。记住,由于第一点的原因,像 <U006F> 现在会被看作是 6F

  4. $i=sprintf("%c", strtonum("0x"$i)): 用对应的ASCII值替换十六进制数字。我们必须在字段 $i 前面加上 "0x",这样awk才能知道这是一个十六进制值。

  5. }1: 这是一个简写,表示必须要有 print,也就是总是打印每一行

  6. 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

撰写回答