在POSIX shell脚本中嵌入Unicode字符的最佳方法是什么?

时间:2014-12-26 03:08:07

标签: bash shell unicode posix dash-shell

有几种特定于shell的方法可以在字符串中包含“unicode literal”。例如,在Bash中,引用的字符串扩展机制$''允许我们直接嵌入一个不可见的字符:$'\u2620'

但是,如果您尝试编写通用的跨平台shell脚本(通常,这可以被截断为“以Bash,Zsh和Dash运行。”),那不是便携式的特征

我可以使用如下构造在ASCII表(八进制数字空间)中移植实现任何内容:

WHAT_A_CHARACTER="$(printf '\036')"

...但是,POSIX / Dash printf仅支持八进制转义。

通过将任务转移到更完整的编程环境,我显然也可以实现完整的Unicode空间:

OH_CAPTAIN_MY_CAPTAIN="$(ruby -e 'print "\u2388"')"
TAKE_ME_OUT_TONIGHT="$(node -e 'console.log("\u266C")')"

那么:将这样的字符编码成shell脚本的最佳方法是:

  1. 适用于dashbashzsh
  2. 显示代码中代码点的十六进制编码,
  3. 不依赖于字符串的特定编码(即不是通过以八进制编码UTF-8字节)
  4. 最后,并不需要调用任何“重型”解释器。 (比方说,运行时间不到0.01秒。)

2 个答案:

答案 0 :(得分:7)

如果您安装了Gnu printf(例如,它位于debian包coreutils中),那么您可以通过避免shell的内置来独立于您使用的shell来使用它:

env printf '\u2388\n'

在这里,我使用Posix标准env命令来避免使用printf内置函数,但如果你碰巧知道printf在哪里,你可以直接使用完整的,路径,如

/usr/bin/printf '\u2388\n'

如果你的外部printf和你的shell内置printf都只实现了Posix标准,那么你需要更加努力。一种可能性是使用iconv转换为UTF-8,但Posix标准要求 iconv命令,它不会以任何方式规定方式标准编码被命名。我认为以下内容适用于大多数与Posix兼容的平台,但创建的子shell数量可能足以使其效率低于“重型”脚本解释器:

printf $(printf '\\%o' $(printf %08x 0x2388 | sed 's/../0x& /g')) |
iconv -f UTF-32BE -t UTF-8

上面使用printf内置来强制十六进制代码点值为8个十六进制数字长,然后sed将它们重写为4个十六进制常量,然后再次printf来更改十六进制常量为八进制表示法,最后另一个printf将八进制字符常量解释为四字节序列,可以作为大端UTF-32馈入iconv。 (使用printf识别\x转义码会更简单,但Posix不需要,dash不会实现它。)

您可以使用不经修改的行来打印多个符号,只要您为所有符号提供Unicode代码点(作为整数常量)(例如在dash中执行):

$ printf $(printf '\\%o' $(printf %08x 0x2388 0x266c 0xA |
>                          sed 's/../0x& /g')) |
> iconv -f UTF-32BE -t UTF-8
⎈♬
$

注意:正如Geoff Nixon在评论中提到的那样,鱼壳(它无法接近Posix标准,据我所知,没有任何愿望可以遵守)会抱怨未加引号%08x的{​​{1}}格式参数,因为它希望以printf开头的单词是jobspecs。因此,如果您使用fish,请在format参数中添加引号。

答案 1 :(得分:-3)

我会选择

echo -e "\xc3\xb6"

做检查:

~ $ echo -e "\xc3\xb6"
ö
~ $ echo -n ö | hexdump
0000000 b6c3                                   
0000002