【问题标题】:How to move the cursor in the bash shell when echoing emojis?回显表情符号时如何在 bash shell 中移动光标?
【发布时间】:2019-06-27 20:51:02
【问题描述】:

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

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

~ $ echo -e "1????3\033[3Dabc"
1abc

类似的问题可以用回车来说明:

~ $ echo -e "1????3\rabc"
abc3

有什么好的方法可以解决这个问题吗?我在 macOS 上使用终端应用程序。有什么便携的方法吗?

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

~ $ while true; do read -p "Enter emoji: " x; echo $x | hexdump; echo -e "1${x}3\033[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

【问题讨论】:

标签: bash shell unicode emoji


【解决方案1】:

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

????
12345678

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

Unicode TR51-16 建议将这些表情符号视为宽:

目前的做法是表情符号具有方形纵横比,这源于它们在日语中的起源。为了互操作性,建议在当前和未来的表情符号中继续这种做法。它们通常具有与 CJK 表意文字大致相同的垂直位置和前进宽度。

根据建议,我会很乐意将“表情符号”Unicode 块中的任何内容硬编码为宽。您的其他有效符号,例如? 和 ☞ 不在表情符号块中(它们分别在麻将和杂项符号中)。

如果你想在运行时确定宽度,你可以例如问 Python,尽管 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。

【讨论】:

  • 人们不能简单地查询 UCD/EAW 的字符并期望它在所有情况下都做一些理智和有用的事情,因为列宽有时对于独立字符来说是模棱两可的,并且只能通过上下文来解析。 p3rl.org/Unicode::GCString#columns 中提供了一个实现
  • @thatotherguy - 所以任何单个 UTF 字符在终端上最多只有 2 个字符宽,而不是更多?
  • @inquisitive 有一些特别长的连字,比如“﷽”,我的系统渲染了大约 11 列长,但我不知道是否有任何规范说这样的字符应该是确定的终端上的列数或等宽字体。我在几个终端上试过,他们都很困惑,例如。将其视为 1 列并使其在 5 列处部分切断
  • @thatotherguy - 谢谢你的例子 - 这让事情变得更加清晰。是的,我的终端模拟器也对此感到很困惑。
【解决方案2】:

试试这个:

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

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

1? 3

两秒钟后,上面的内容被覆盖:

abc

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

如果“更多”空格太多,计算覆盖字符串会得到更精确的结果:

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

【讨论】:

  • 顺便说一句,echo -ne ... 最好替换为printf '%b' "..."(它将在更多的 shell 和运行时配置中可靠地工作);在unix.stackexchange.com/questions/65803/… 上查看 Stephane 的出色帖子/答案
  • @agc - 我应该澄清一下。 4d 不适用于所有 UTF-8 字符。例如,如果您尝试使用象形文字,例如♞,我只需向后移动 3 个空格。我应该如何根据角色确定要向后移动多少个字符?
  • @cmaster,查看修改后的答案。
  • 是的,现在答案更好了,但仍然没有达到目标:它获取 UTF-8 编码字符串的字节数,但这与字符列数不同被相同的字符串占用。不过,我现在要删除我的反对票。
猜你喜欢
  • 1970-01-01
  • 2015-10-18
  • 2019-11-29
  • 2021-06-22
  • 2023-03-07
  • 1970-01-01
  • 1970-01-01
  • 2016-09-30
  • 2022-12-01
相关资源
最近更新 更多