在有效的shell \u
(bash +4.3、ksh93 或 zsh)中,我们可以打印 Unicode 字符:
$ printf 'a b c \ua0 \ua1 \ua2 \ua3 \n'
a b c ¡ ¢ £
哪些是来自Latin-1_补充范围。
然而,一旦添加了 Unicode字符,打印就会停止,直到打印出9f
Unicode 。9c
和(APC 和 ST)\u9f
都是\u9c
C1
控制字符。
$ printf 'a b c \u9f d e f \u9c \ua0 \ua1 \ua2 \ua3 \n'
a b c ¡ ¢ £
角色肯定会消失。
可以肯定的是,它printf
正在生成所有字符,并且将输出重定向到其他软件(而不是终端)将显示生成的字符:
$ printf 'a b c \u9f d e f \u9c \ua0 \ua1 \ua2 \ua3 \n' | od -A n -tx1
61 20 62 20 63 20 c2 9f 20 64 20 65 20 66 20 c2
9c 20 c2 a0 20 c2 a1 20 c2 a2 20 c2 a3 20 0a
这足以证明角色正在生成。那么,为什么它们没有被打印(用一些可见的字形显示)?
我的问题是:
- 实际上
APC
连接到ST
.它在哪里定义的? - 这两个字符之间的字符是否发送到某个应用程序?
- 如果是这样,到哪个应用程序?
- 谁负责这种重定向? shell、终端还是其他东西?
编辑
xterm
或终端都不会konsole
删除d e f
字符。
这证实这是终端应用程序的内部问题,而不是 shell。还没有找到在哪里定义的。
答案1
APC实际上是连接到ST的吗?它在哪里定义的?
这些控制字符实际上并不是 Unicode 的原始字符,而是从旧的字符集规范继承而来,例如ECMA-48、ISO/IEC 6429 和 ISO/IEC-8859 字符编码系列。从广义上讲,这些标准在 C1 控制字符上基本上彼此一致(因为它们彼此向后兼容,甚至有些更旧的规范)。
由于 ISO/IEC 6429 的副本正在出售,我不希望在互联网上找到免费的合法副本,但 ECMA-48 表示:
8.3.2 APC——应用程序命令
符号:(C1)
代表:09/15 或 ESC 05/15
APC 用作应用程序使用的控制字符串的起始定界符。后面的命令串可以由 00/08 至 00/13 和 02/00 至 07/14 范围内的位组合组成。控制字符串由终止定界符 STRING TERMINATOR (ST) 关闭。命令串的解释取决于相关的应用程序。
和:
8.3.143 ST——字符串终止符
符号:(C1)
代表:09/12 或 ESC 05/12
ST 用作由应用程序命令 (APC)、设备控制字符串 (DCS)、操作系统命令 (OSC)、隐私消息 (PM) 或字符串开始 (SOS) 打开的控制字符串的结束分隔符。
Unicode 在 C1 控制字符范围内仅定义一个控制字符:U+0085 下一行 (NEL)。对于 C1 范围内的任何其他字符,规范的这一部分适用:
控制代码的语义通常由它们所使用的应用程序确定。然而,在没有特定应用用途的情况下,可以根据 ISO/IEC 6429:1992 中规定的控制功能语义来解释它们。
我无法在这里验证它,但我希望 ISO/IEC 6429 与 ECMA-48 所说的非常接近,如上所述。此外,终端的作者可能认为“向后兼容 Unicode 之前的 7 位和 8 位字符编码,如 ECMA-48”具体应用用途。
因此,终端可能会将 APC 和 ST 之间的字符合法地解释为“我不知道这些字符的用途,但我确信这些字符不打算显示为常规输出”。
终端可能会或可能不会被编程以某种方式做出反应一些特定的字符串封装在APC和ST之间,并忽略任何不匹配的字符串。由于终端窗口是“人类之前的最后一步”,因此当然可以假设任何应用程序命令到达它的字符串意味着终端要解释并执行操作(如果适用),任何终端无法识别的此类字符串必定是错误的。
显示“无效编码”字符或其他错误消息是不合适的,因为该字符串被有效编码为“特定于应用程序的控制字符串,不用于显示”。因此,标题问题“角色要去哪里?”的答案很可能是:它们作为无效控制字符串的一部分被丢弃。
但请注意,Unicode 规范表示“...可能被解释为……”,而不是“……必须被解释...”。因此,其他终端实现选择忽略 APC 和 ST 字符作为没有适用含义的不可打印控制字符也不一定是无效的。
Stack Overflow 上的这个问题还讨论了涉及 APC 和 ST 控制字符的控制序列。
那里接受的答案说:
现实情况是,APC 很少被实现——大多数系统从不生成 APC 序列,并且默默地忽略任何收到的序列。任何应用程序都不应发送或解释 APC 序列,除非它知道连接的另一端正在以特定方式使用它们 - 例如通过配置选项来启用它们,或者它(以某种方式)知道正在使用哪个终端仿真器并且知道终端仿真器为它们分配了特定的含义 [...]
答案2
这些角色没有被发送到任何地方,他们根本就没有被发送显示的尽管存在于输出中,但通过您的终端:
$ printf 'a b c \u9f d e f \u9c \ua0 \ua1 \ua2 \ua3 \n' | od -c
0000000 a b c 302 237 d e f 302
0000020 234 302 240 302 241 302 242 302 243 \n
0000037
您还可以通过重定向到文件然后调查该文件来确认它们位于输出中:
$ printf 'a b c \u9f d e f \u9c \ua0 \ua1 \ua2 \ua3 \n' > file
$ od -c file
0000000 a b c 302 237 d e f 302
0000020 234 302 240 302 241 302 242 302 243 \n
0000037
看起来终端使用 和 的组合执行的操作\u9f
取决于\u9c
实现。碰巧这样你的终端通过向后移动几个字符并从那里继续打印来处理它,这会导致覆盖其他字符。这就是为什么你会看到:
$ printf 'a b c \u9f d e f \u9c \ua0 \ua1 \ua2 \ua3 \n'
a b c ¡ ¢ £
我可以在 上重现该内容gnome-terminator
,但xterm
只打印一个空格:
$ printf 'a b c \u9f d e f \u9c \ua0 \ua1 \ua2 \ua3 \n'
a b c d e f ¡ ¢ £
屏幕截图中也有同样的内容:
这类似于在更清晰的情况下发生的情况,即使用回车( \r
) 的工作正是移回行首。这就是为什么你会得到:
$ printf '12345\r67890\n'
67890
终端开始打印12345
,然后将\r
其发送回行的开头,并用 覆盖 ,12345
因此67890
您最终看到的只是67890
。但132345
没有发送到任何其他程序,它仍然在那里,它只是不可见,因为其他字符已覆盖它:
$ printf '12345\r67890\n' | od -c
0000000 1 2 3 4 5 \r 6 7 8 9 0 \n
0000014
答案3
您正在解释输出 UTF8 字符序列的命令以及您在终端仿真器窗口(通常称为“我的终端窗口”)中看到的显示结果。
然后,您描述的字符序列似乎不会导致在终端仿真器的窗口中显示可见结果。你会问:“这些字符是否被发送到某个应用程序?”
是的,它们被传送到您的终端模拟器,终端模拟器解释它接收到的字符序列并决定它将在其窗口中显示哪些字形供您查看。