如何在回显表情符号时将光标移动到bash shell?

How to move the cursor in the bash shell when echoing emojis?

我正在使用 here 中描述的光标移动功能为 Bash 编写游戏引擎。但是,如果我回显表情符号或其他超过 1 个字节的 UTF-8 字符,光标位置似乎会弄乱。

例如,下面的代码应该回显“13”,将光标向后移动 3 个位置,然后在同一个地方回显 "abc"。结果应该仅为 "abc"(理想情况下)。相反,我看到“1abc”

~ $ echo -e "133[3Dabc"
1abc

回车进纸可以说明类似的问题:

~ $ echo -e "13\rabc"
abc3

请问有什么好的解决办法吗?我在 macOS 上使用终端应用程序。有什么便携的方法可以做到这一点吗?

注意:请注意,并非所有 UTF-8 字符似乎都以这种方式运行。大多数情况下,我只能用表情符号重现这个问题:

~ $ while true; do read -p "Enter emoji: " x; echo $x | hexdump; echo -e "1${x}33[3Dabc"; done
Enter emoji: 
0000000 f0 9f 94 88 0a                                 
0000005
1abc
Enter emoji: ♞
0000000 e2 99 9e 0a                                    
0000004
abc
Enter emoji: ☞
0000000 e2 98 9e 0a                                    
0000004
abc
Enter emoji: 
0000000 f0 9f 98 8b 0a                                 
0000005
1abc
Enter emoji: 
0000000 f0 9f 83 98 0a                                 
0000005
abc
Enter emoji: 
0000000 f0 9f 80 96 0a                                 
0000005
abc
Enter emoji: 
0000000 f0 9d 95 ad 0a                                 
0000005
abc
Enter emoji: 
0000000 f0 9f 87 ba f0 9f 87 b8 0a                     
0000009
1abc
Enter emoji: ✎
0000000 e2 9c 8e 0a                                    
0000004
abc

试试这个:

s="13" ; printf "$s"; sleep 2; printf "3[$((${#s}+1))Dabc%${#s}s\n" ' '

我在 printf 之间设置了延迟,以便更容易看到发生了什么。首先是:

1 3

两秒后上面被覆盖:

abc

它是如何工作的:我们把 unicode 的东西放在一个字符串中 $s${#s} returns 该字符串的字节长度。在 $((${#s}+1)) 中使用长度来计算要移动多少个空格,然后 %${#s}s 告诉 printf 它需要多少个空格(加上更多)来覆盖任何剩余的字符。

如果"a few more"空格太多,计算覆盖字符串给出更精确的结果:

s="13" t="abc" 
printf "${s}"; sleep 2; printf "3[$((${#s}+1))D$t%$((1+${#s}-${#t}))s\n" ''

问题的发生是因为 a 实际上是跨两列呈现的。在我的系统上,四个表情符号和八个数字一样长:


12345678

预计单个宽字符将需要两个窄字符覆盖它。

Unicode TR51-16:

建议将这些表情符号视为宽

Current practice is for emoji to have a square aspect ratio, deriving from their origin in Japanese. For interoperability, it is recommended that this practice be continued with current and future emoji. They will typically have about the same vertical placement and advance width as CJK ideographs.

根据建议,我可以轻松地将 "Emoticon" Unicode 块中的任何内容硬编码为宽。您的其他有效符号,例如 和 ☞ 不在表情块中(它们分别在麻将和杂项符号中)。

如果你想在运行时确定宽度,你可以,例如问 Python,这有助于将其东亚宽度报告为 Full/Wide,即使 Unicode 表本身将其标记为中性:

$ python3 -c 'import sys; import unicodedata as u; print(u.east_asian_width(sys.argv[1]))' 
W

$ python3 -c 'import sys; import unicodedata as u; print(u.east_asian_width(sys.argv[1]))' ♞
N

有点特殊,因为它由两个不同的区域指标符号和单独的代码点组成,但 Python 将它们中的每一个标记为中性,因此如果您将其视为 1,它仍会添加最多 2.