将ASCII字符转换为"<Uxxx>"Unicode表示的脚本

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

我正在修改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字符(范围从 0x200x7F),所以这实际上是一个更复杂的“字符转十六进制字符串”的转换。

有没有人能帮我一下?我对bash脚本的技能非常有限,Python更是糟糕。

优雅且有解释的解决方案会有额外奖励。

谢谢!

(顺便说一下,这个脚本是我之前问题的“反向”脚本,链接在这里 previous question

4 个回答

0

这段代码的意思是:首先用`echo -n "aä"`这个命令输出字符串“aä”,然后把这个字符串传给Ruby程序来处理。Ruby会把这个字符串中的每个字符都转换成它的Unicode编码,并以特定的格式打印出来。

具体来说,`ruby -KU -e`这部分是告诉Ruby使用Unicode编码来处理输入的字符。`$<.chars{|c| print "<U"+"%04X"%c.unpack("U*")[0]+">"}`这段代码的作用是:对每个字符进行循环,获取它的Unicode编码,并以``的格式输出,其中`XXXX`是这个字符的编码值。

最后,`puts`是用来换行的。

运行这段代码后,输出的结果是``,这表示字符“a”的Unicode编码是`0061`,而字符“ä”的Unicode编码是`00E4`。

另外,`-KU`这个选项其实是设置Ruby的编码方式,等同于在代码中写`$KCODE = "U"`,表示使用Unicode编码。

5

使用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上都可以用。只需把开头的版本号改成你自己用的版本。)

解释:

  1. 我们需要导入 `sys`模块,这样才能读取命令行参数。

  2. `sys.argv` 列表 是所有命令行参数的集合。第一个元素 [0] 是程序的名字,第二个元素 [1] 是第一个参数,依此类推。

  3. f(char) for char in text 是一个 生成器表达式。它会对 `text` 变量中的每个字符进行循环,然后对每个字符应用函数 f,最后把结果收集成一个懒加载的列表(可迭代对象)。

  4. ord(char) 用来找到字符的Unicode编码。

  5. "<U{0:04X}>".format(x) 是一种字符串格式化的方法。这个格式字符串需要一个输入 x,然后把它格式化成 04X 格式,意思是用零填充,总宽度为4,结果是大写的十六进制。

  6. "".join(it) 把懒加载列表(可迭代对象) it 中的所有元素连接起来。"" 表示用空字符串作为分隔符。

  7. print(encoded) 将字符串 encoded 输出到标准输出。

8

每个字符与文件输入

如果你想把一个文件里的每个字符都转换成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>";/

解释

其实很简单

  1. while IFS= read -r -n1 c;:逐个字符读取输入(通过 -n1),并把字符存储在变量 c 中。这里的 IFS=-r 是为了让 read 不去拆分单词或者解释转义字符。
  2. if [[ "$c" == '"' ]];:如果当前字符是双引号
  3. ((flag^=1)):把标志的值反转,从0变成1或者从1变成0
  4. elif [[ "$c" == $'\0' ]];:如果当前字符是NUL(空字符),那么就 echo 一个换行符
  5. elif ((flag)):如果标志是1,就进行unicode转换
  6. printf "<U%04X>" "'$c":这就是进行unicode转换的神奇代码。注意在 $c 前面有个单引号,这是必须的,它告诉 printf 我们给的是一个数字的ASCII表示。
  7. else printf "%c" "$c":如果没有进行unicode转换,就直接打印出这个字符

撰写回答