【问题标题】:What's the best way to embed a Unicode character in a POSIX shell script?在 POSIX shell 脚本中嵌入 Unicode 字符的最佳方法是什么?
【发布时间】:2015-02-23 11:45:44
【问题描述】:

有几种特定于 shell 的方法可以在字符串中包含“unicode 文字”。例如,在 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,您当然可以在脚本源中逐字显示您的角色,例如,printf '⎈♬\n'。如果你有一个不错的编辑器,将光标放在它上面应该会显示代码;你也应该可以输入它(例如,Ctrl+Shift+u 2388)。我不明白为什么 2 真的是个问题。
  • @gniourf_gniourf 2 的问题基本上是它需要一个像样的编辑器。在很多情况下,我希望那些没有那么奢侈的人可以访问我的源代码。具有对程序功能至关重要的特殊字符以可访问的方式编码,从而为更大的贡献者群体打开了源代码的开发。这不是总是(甚至经常!)的问题,但有时值得考虑。 ;)
  • @gniourf_gniourf(有很多情况,即使是在 2015 年,逐字 Unicode 编码的文档会被假设为简单的 ASCII 或 ISO-8859-1 的管道破坏。这是一个可悲的事实。)跨度>
  • 这是一个很好的问题,我同意你关于包含裸字形的观点。但是: - #2 有点自我冗余:这是您指定的输入格式,因此如果您将一个十六进制代码放在那里,它只会显示一个十六进制代码。代码点只是与字形相关联的 32 位整数,而十六进制 U+09AF 格式只是一种约定。 en.wikipedia.org/wiki/Unicode#Architecture_and_terminology - 第三个是荒谬的。您必须对字符串进行编码以某种方式;正如您已经提到的,POSIX printf 只允许八进制转义。因此,使用语言环境 UTF-8,这是唯一的方法。
  • (3) 背后的原理是什么?

标签: bash shell unicode posix dash-shell


【解决方案1】:

如果您安装了 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 将八进制字符常量解释为一个四字节序列,该序列可以作为big-endian UTF-32 输入iconv。 (使用识别\x 转义码的printf 会更简单,但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 将参数格式化为 printf,因为它期望以 % 开头的单词是作业规范。因此,如果您使用 fish,请在格式参数中添加引号。

【讨论】:

  • command 命令专为您使用env 的目的而设计。
  • 好的——有点。我明白你在说什么。严格来说,根据 POSIX 的参考条款,printf 不是 shell 特殊内置:特殊内置的命令是:breakcoloncontinuedot、@987654354 @、execexitexportreadonlyreturnsetshifttimestrapunsetcolon(其中@98765436@分别拼写为:.)。 shell 应该对printf 做什么是“不执行内置”——它是否真的执行其他东西是有争议的。例如,在 Bash 中,command cd 仍然设法更改 shell 的目录。
  • @JonathanLeffler:也许它是否应该执行别的东西是值得商榷的(尽管 Posix 的“在所有其他方面,如果 command_name 不是函数的名称,command 的效果(没有选项)应与省略命令相同”不会留下太多回旋余地),但它是否确实执行其他操作是没有争议的。例如,在我安装的 ubuntu 中的 dash 中,env printf '\u2388\n'dash 中的 command printf '\u2388\n' 不会打印相同的内容,我相信您可以轻松地重现该实验。
  • @GeoffNixon:另外,在长版本printf $(printf ...) 中,不需要阻止shell 使用其内置的printf,让它这样做效率要低得多。在第一个建议中使用env 的唯一原因是处理像dash 这样的shell,其内置printf 不处理unicode 转义。
  • @GeoffNixon:该漏洞与env 无关。这是关于bash 中的错误,因为它在启动时读取环境。没错,您可以使用 env 将不符合要求的字符串植入环境中,但在此调用中,很明显不会发生这种情况,并且无论如何都没有可能误解字符串的子 bash 进程.
【解决方案2】:

我会选择

echo -e "\xc3\xb6"

检查一下:

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

【讨论】:

  • 请注意,例如,\u2388 ≠ \x23\x88。相反,\u2388 = \xe2\x8e\x88\x00.
  • 这在dash 中不起作用。此外,它可能不符合要求 (3):“不依赖于字符串的特定编码(即不通过以八进制编码 UTF-8 字节)”。 (它需要编码 UTF-8 字节,尽管是十六进制,所以它掩盖了原始的 Unicode 代码点。)
  • 此外,将-e 视为要打印的字符串以外的任何内容都违反了echo 的POSIX 规范(不是“不保证”,而是实际上“违反”)。 -n 和带有反斜杠的字符串是实现定义的禁止 XSI 扩展,而 -e 根本不允许。请参阅pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html——包括标准的应用程序部分,它建议使用printf
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-04
  • 2019-06-02
相关资源
最近更新 更多