BASH中printf与od的Unicode字符表示差异及反向转换疑问
关于printf与od处理Unicode字符的两个原理性疑问
问题1:为何printf与od对Unicode字符的数值表示不一致?
对于ASCII字符,printf和od的十进制、八进制、十六进制输出完全一致,示例如下:
ascii_char=A printf "%d" "'$ascii_char" # 输出:65 echo -n $ascii_char | od -A n -t d1 # 输出: 65 echo -n $ascii_char | od -A n -t u1 # 输出: 65 printf "%o" "'$ascii_char" # 输出:101 echo -n $ascii_char | od -A n -t o1 # 输出: 101 printf "%x" "'$ascii_char" # 输出:41 echo -n $ascii_char | od -A n -t x1 # 输出: 41
但处理Unicode字符时,两者输出差异极大,示例如下:
unicode_char=🐕 printf "%d" "'$unicode_char" # 输出:128021 echo -n $unicode_char | od -A n -t d1 # 输出: -16 -97 -112 -107 echo -n $unicode_char | od -A n -t d # 输出: -1785683984 echo -n $unicode_char | od -A n -t u1 # 输出: 240 159 144 149 echo -n $unicode_char | od -A n -t u # 输出: 2509283312 printf "%o" "'$unicode_char" # 输出:372025 echo -n $unicode_char | od -A n -t o1 # 输出: 360 237 220 225 echo -n $unicode_char | od -A n -t o # 输出: 22544117760 printf "%x" "'$unicode_char" # 输出:1f415 echo -n $unicode_char | od -A n -t x1 # 输出: f0 9f 90 95 echo -n $unicode_char | od -A n -t x # 输出: 95909ff0
核心原因
两者处理的是完全不同的对象:
printf "%d" "'$unicode_char"读取的是Unicode码点——也就是该字符在Unicode标准中对应的唯一整数ID(比如🐕的码点是U+1F415,十进制就是128021)。od是直接读取字符在UTF-8编码下的字节序列,然后对字节做数值转换:- 带
1的参数(比如-t d1/-t o1)是逐个输出每个字节的数值; - 不带
1的参数(比如-t d/-t o)是把多个字节按机器的字节序(通常是小端)合并成一个整数输出。
ASCII字符的码点和UTF-8编码是单字节一一对应,所以两者输出一致;但Unicode字符(尤其是超出基本多语言平面的字符)在UTF-8中是多字节编码,自然和码点数值完全不同。
- 带
问题2:为何printf能反向转换od的结果,却无法转换自身输出的部分结果?
示例如下:
printf "%o" "'$unicode_char" # 输出:372025(printf无法直接转换这个结果) echo -n $unicode_char | od -A n -t o1 # 输出: 360 237 220 225(结果不同,但printf可正确反向转换) printf %b '\360\237\220\225' # 输出:🐕(转换成功) printf "%x" "'$unicode_char" # 输出:1f415(printf可转换自身输出的该结果) printf "\U$(printf %08x 0x1f415)" # 输出:🐕(转换成功) echo -n $unicode_char | od -A n -t x1 # 输出: f0 9f 90 95(结果不同,但printf可正确反向转换) printf %b '\xf0\x9f\x90\x95' # 输出:🐕(转换成功)
核心原因
这是printf的转义规则决定的:
- 处理od的结果:
od输出的是UTF-8字节的八进制/十六进制值,printf %b '\360\237\220\225'是把每个3位八进制转义(或两位十六进制转义)解析成单个字节,这些字节拼接起来就是🐕的UTF-8编码,终端会自动把这个字节序列解析为对应的Unicode字符。 - 处理自身输出的码点:
- 十六进制码点:
printf支持\UXXXXXXXX语法(需要8位十六进制补零),所以\U0001F415能直接识别为Unicode码点并输出对应字符; - 八进制码点:
printf没有直接识别多字节八进制码点的语法——它的八进制转义最多只认3位(对应单字节),所以372025这个5位的八进制码点值,无法被printf直接解析为对应字符。
- 十六进制码点:
内容的提问来源于stack exchange,提问作者KiriSakow
相关产品推荐
相关产品推荐

