将ASCII字符转换为"<Uxxx>"Unicode表示的脚本
我正在修改Linux的区域设置文件 /usr/share/i18n/locales
(比如 pt_BR
),需要将格式字符串(像 %d-%m-%Y %H:%M
)用Unicode表示,每个字符(在这个例子中是ASCII字符)都要写成 <U00xx>
的形式。
比如像这样的文本:
LC_TIME
d_t_fmt "%a %d %b %Y %T %Z"
d_fmt "%d-%m-%Y"
t_fmt "%T"
必须变成:
LC_TIME
d_t_fmt "<U0025><U0061><U0020><U0025><U0064><U0020><U0025><U0062><U0020><U0025><U0059><U0020><U0025><U0054><U0020><U0025><U005A>"
d_fmt "<U0025><U0064><U002D><U0025><U006D><U002D><U0025><U0059>"
t_fmt "<U0025><U0054>"
所以我需要一个命令行脚本(可以是bash、Python、Perl或者其他语言),它能把输入的 %d-%m-%Y
转换成 <U0025><U0064><U002D><U0025><U006D><U002D><U0025><U0059>
。
输入字符串中的所有字符都是ASCII字符(范围从 0x20
到 0x7F
),所以这实际上是一个更复杂的“字符转十六进制字符串”的转换。
有没有人能帮我一下?我对bash脚本的技能非常有限,Python更是糟糕。
优雅且有解释的解决方案会有额外奖励。
谢谢!
(顺便说一下,这个脚本是我之前问题的“反向”脚本,链接在这里 previous question)
4 个回答
这段代码的意思是:首先用`echo -n "aä"`这个命令输出字符串“aä”,然后把这个字符串传给Ruby程序来处理。Ruby会把这个字符串中的每个字符都转换成它的Unicode编码,并以特定的格式打印出来。
具体来说,`ruby -KU -e`这部分是告诉Ruby使用Unicode编码来处理输入的字符。`$<.chars{|c| print "<U"+"%04X"%c.unpack("U*")[0]+">"}`这段代码的作用是:对每个字符进行循环,获取它的Unicode编码,并以`
最后,`puts`是用来换行的。
运行这段代码后,输出的结果是`
另外,`-KU`这个选项其实是设置Ruby的编码方式,等同于在代码中写`$KCODE = "U"`,表示使用Unicode编码。
使用Python
#!/usr/bin/env python3.2
import sys
text = sys.argv[1]
encoded = "".join("<U{0:04X}>".format(ord(char)) for char in text)
print(encoded)
使用方法:
$ python3 file.py "enter_input"
<U0065><U006E><U0074><U0065><U0072><U005F><U0069><U006E><U0070><U0075><U0074>
(这个脚本在python 3.x和2.x上都可以用。只需把开头的版本号改成你自己用的版本。)
解释:
我们需要导入 `sys`模块,这样才能读取命令行参数。
`sys.argv` 列表 是所有命令行参数的集合。第一个元素 [0] 是程序的名字,第二个元素 [1] 是第一个参数,依此类推。
f(char) for char in text
是一个 生成器表达式。它会对 `text` 变量中的每个字符进行循环,然后对每个字符应用函数f
,最后把结果收集成一个懒加载的列表(可迭代对象)。ord(char)
用来找到字符的Unicode编码。"<U{0:04X}>".format(x)
是一种字符串格式化的方法。这个格式字符串需要一个输入x
,然后把它格式化成04X
格式,意思是用零填充,总宽度为4,结果是大写的十六进制。"".join(it)
把懒加载列表(可迭代对象)it
中的所有元素连接起来。""
表示用空字符串作为分隔符。print(encoded)
将字符串encoded
输出到标准输出。
每个字符与文件输入
如果你想把一个文件里的每个字符都转换成unicode表示法,那么只需要这么简单的一行代码:
while IFS= read -r -n1 c;do printf "<U%04X>" "'$c"; done < ./infile
每个字符在标准输入(STDIN)
如果你想做一个类似于Unix的工具,把标准输入的内容转换成unicode格式的输出,可以使用这个:
uni(){ c=$(cat); for((i=0;i<${#c};i++)); do printf "<U%04X>" "'${c:i:1}"; done; }
概念验证
$ echo "abc" | uni
<U0061><U0062><U0063>
只处理双引号之间的字符
#!/bin/bash
flag=0
while IFS= read -r -n1 c; do
if [[ "$c" == '"' ]]; then
((flag^=1))
printf "%c" "$c"
elif [[ "$c" == $'\0' ]]; then
echo
elif ((flag)); then
printf "<U%04X>" "'$c"
else
printf "%c" "$c"
fi
done < /path/to/infile
概念验证
$ cat ./unime
LC_TIME
d_t_fmt "%a %d %b %Y %T %Z"
d_fmt "%d-%m-%Y"
t_fmt "%T"
abday "Dom";"Seg";/
here is a string with "multiline
quotes";/
$ ./uni.sh
LC_TIME
d_t_fmt "<U0025><U0061><U0020><U0025><U0064><U0020><U0025><U0062><U0020><U0025><U0059><U0020><U0025><U0054><U0020><U0025><U005A>"
d_fmt "<U0025><U0064><U002D><U0025><U006D><U002D><U0025><U0059>"
t_fmt "<U0025><U0054>"
abday "<U0044><U006F><U006D>";"<U0053><U0065><U0067>";/
here is a string with "<U006D><U0075><U006C><U0074><U0069><U006C><U0069><U006E><U0065>
<U0071><U0075><U006F><U0074><U0065><U0073>";/
解释
其实很简单
while IFS= read -r -n1 c;
:逐个字符读取输入(通过-n1
),并把字符存储在变量c
中。这里的IFS=
和-r
是为了让read
不去拆分单词或者解释转义字符。if [[ "$c" == '"' ]];
:如果当前字符是双引号((flag^=1))
:把标志的值反转,从0变成1或者从1变成0elif [[ "$c" == $'\0' ]];
:如果当前字符是NUL(空字符),那么就echo
一个换行符elif ((flag))
:如果标志是1,就进行unicode转换printf "<U%04X>" "'$c"
:这就是进行unicode转换的神奇代码。注意在$c
前面有个单引号,这是必须的,它告诉printf
我们给的是一个数字的ASCII表示。else printf "%c" "$c"
:如果没有进行unicode转换,就直接打印出这个字符