关于shell:你如何回应Bash中的4位Unicode字符?

How do you echo a 4-digit Unicode character in Bash?

我想把unicode骷髅和交叉骨骼添加到shell提示(特别是"骷髅和交叉骨骼"(U+2620)),但我无法找到让echo吐出来的魔法咒语,或任何其他4位unicode字符。两位数很容易。例如,echo-e"x55",。

除了下面的答案外,还应该注意到,显然,您的终端需要支持Unicode,以便输出符合您的期望。gnome终端可以很好地完成这项工作,但默认情况下不一定要打开它。

在MacOS的终端应用程序上,进入Preferences->Encodings并选择Unicode(UTF-8)。


在UTF-8中,它实际上是6位数(或3字节)。

1
2
$ printf '\xE2\x98\xA0'
?

要检查控制台如何对其进行编码,请使用hexdump:

1
2
3
$ printf ? | hexdump
0000000 98e2 00a0                              
0000003


4

这在zsh(我检查过4.3版)和bash 4.2或更新版本中都有效。


只要文本编辑器能够处理Unicode(大概是用UTF-8编码的),就可以直接输入Unicode码位。

例如,在VIM文本编辑器中,您将进入插入模式,然后按ctrl+v+u,然后按代码点号作为4位十六进制数(如有必要,请用零填充)。因此,您可以键入ctrl+vbkbbd+u 2 6 2 0。请参见:将Unicode字符插入文档的最简单方法是什么?

在运行bash的终端上,您可以键入ctrl+shift+u,并键入所需字符的十六进制代码点。在输入期间,光标应显示带下划线的u。输入的第一个非数字结束输入,并呈现字符。因此,您可以使用以下方法在bash中打印U+2620:

AKBDCBDCCtrL+SHIFT+UKBD62XEnterXEnter

(第一个回车结束Unicode输入,第二个回车运行echo命令。)

学分:询问Ubuntu SE


这里是一个完全内部的bash实现,没有分叉,Unicode字符的大小不受限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
fast_chr() {
    local __octal
    local __char
    printf -v __octal '%03o' $1
    printf -v __char \\$__octal
    REPLY=$__char
}

function unichr {
    local c=$1    # Ordinal of char
    local l=0    # Byte ctr
    local o=63    # Ceiling
    local p=128    # Accum. bits
    local s=''    # Output string

    (( c < 0x80 )) && { fast_chr"$c"; echo -n"$REPLY"; return; }

    while (( c > o )); do
        fast_chr $(( t = 0x80 | c & 0x3f ))
        s="$REPLY$s"
        (( c >>= 6, l++, p += o+1, o>>=1 ))
    done

    fast_chr $(( t = p | c ))
    echo -n"$REPLY$s"
}

## test harness
for (( i=0x2500; i<0x2600; i++ )); do
    unichr $i
done

产量为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
─━│┃┄┅┆┇┈┉┊┋┌┍┎┏
┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟
┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯
┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿
╀╁╂╃╄╅╆╇╈╉╊╋????
═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟
╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯
╰╱╲╳????????????
?▁▂▃▄▅▆▇█▉▊▋▍▎▏
???▓▔▕??????????
□??????????????
??▲△????????▼▽??
??????◆◇???○??◎●
????????????????
??◢◣◤◥??????????
????????????????


只放"?"在shell脚本中。在正确的区域设置和启用Unicode的控制台上,它的打印效果很好:

1
2
3
$ echo ?
?
$

一个丑陋的"解决方法"是输出UTF-8序列,但这也取决于所使用的编码:

1
2
3
$ echo -e '\xE2\x98\xA0'
?
$

快速一行程序将utf-8字符转换为3字节格式:

1
var="$(echo -n '?' | od -An -tx1)"; printf '\\x%s' ${var^^}; echo


我用这个:

1
2
$ echo -e '\u2620'
?

这比搜索十六进制表示更容易…我在shell脚本中使用这个。它适用于gnome术语和urxvt-afaik。


您可能需要将代码点编码为八进制,以便提示扩展以正确解码它。

U+2620编码为UTF-8是e2 98 a0。

所以在狂欢中,

1
export PS1="\342\230\240"

会使你的外壳迅速进入头骨和骨头。


这三个命令中的任何一个都将在控制台中打印所需的字符,前提是控制台确实接受UTF-8字符(大多数当前的命令都接受):

1
2
3
4
5
6
echo -e"SKULL AND CROSSBONES (U+2620) \U02620"
echo $'SKULL AND CROSSBONES (U+2620) \U02620'
printf"%b""SKULL AND CROSSBONES (U+2620) \U02620
"


SKULL AND CROSSBONES (U+2620) ?

之后,您可以将实际的字形(图像、字符)复制并粘贴到任何(启用了UTF-8)文本编辑器中。

如果您需要了解这样的Unicode代码点是如何用UTF-8编码的,请使用XXD(比OD更好的十六进制查看器):

1
2
3
4
echo $'(U+2620) \U02620' | xxd
0000000: 2855 2b32 3632 3029 20e2 98a0 0a         (U+2620) ....

That means that the UTF8 encoding is: e2 98 a0

或者,以十六进制表示以避免错误:0xe2 0x98 0xa0。也就是说,空格(hex 20)和换行(hex 0a)之间的值。

如果你想深入研究把数字转换成字符:看这里!


在bash中,要打印要输出的Unicode字符,请使用x、u或u(第一个用于2位十六进制,第二个用于4位十六进制,第三个用于任何长度)

4

我想将它赋给一个变量,使用$...'语法

1
2
x=$'\U1f602'
echo $x

printf内置(就像coreutils的printf一样)知道接受4位Unicode字符的\u转义序列:

1
   \uHHHH Unicode (ISO/IEC 10646) character with hex value HHHH (4 digits)

使用bash 4.2.37(1)进行测试:

1
2
3
$ printf '\u2620
'

?


如果您不介意使用perl-one行程序:

16

-CS在输入端启用UTF-8解码,在输出端启用UTF-8编码。-E将下一个参数评估为perl,启用了诸如say之类的现代功能。如果最后不想换行,可以用print代替say


很抱歉又提了这个老问题。但是,在使用bash时,有一种非常简单的方法可以从纯ASCII输入创建Unicode码点,这种方法甚至根本不分叉:

1
2
unicode() { local -n a="$1"; local c; printf -vc '\\U%08x'"$2"; printf -va"$c"; }
unicodes() { local a c; for a; do printf -vc '\\U%08x'"$a"; printf"$c"; done; };

按如下方式定义某些代码点

1
2
unicode crossbones 0x2620
echo"$crossbones"

或者将前65536个Unicode代码转储到stdout(在我的计算机上不到2秒)。额外的空间是为了防止某些字符由于shell的单空间字体而相互流入):

1
for a in {0..65535}; do unicodes"$a"; printf ' '; done

或者说一些非常典型的家长故事(这需要Unicode 2010):

1
unicodes 0x1F6BC 32 43 32 0x1F62D 32 32 43 32 0x1F37C 32 61 32 0x263A 32 32 43 32 0x1F4A9 10

说明:

  • printf '\UXXXXXXXX'打印出任何Unicode字符
  • printf '\\U%08x' number打印\UXXXXXXXX并将数字转换为十六进制,然后送入另一个printf以实际打印出unicode字符。
  • printf将八进制(0oct)、十六进制(0xhex)和十进制(0或以1到9开头的数字)识别为数字,因此您可以选择最适合的表示形式。
  • printf -v var ..printf的输出集合成一个变量,不需要叉子(可以极大地加快速度)。
  • local variable的存在不会污染全局命名空间
  • local -n var=othervar化名为other,这样对var的赋值就改变了other。其中一个有趣的部分是,var是本地命名空间的一部分,而other是全局命名空间的一部分。
    • 请注意,bash中没有localglobal名称空间。变量保存在环境中,并且始终是全局的。local只会将当前值放在一边,并在函数再次离开时将其恢复。从带有local的函数中调用的其他函数仍将看到"本地"值。这是一个与其他语言中的所有正常范围规则根本不同的概念(而且,bash所做的功能非常强大,但如果您是一个不知道这一点的程序员,则可能会导致错误)。


使用python2/3一个内衬很容易:

1
2
$ python -c 'print u"\u2620"'    # python2
$ python3 -c 'print(u"\u2620")'  # python3

结果:

1
?

根据堆栈溢出问题unix cut,删除第一个令牌和https://stack overflow.com/a/15903654/781312:

1
2
3
(octal=$(echo -n ? | od -t o1 | head -1 | cut -d' ' -f2- | sed -e 's#\([0-9]\+\) *#\\0\1#g')
echo Octal representation is following $octal
echo -e"$octal")

输出如下。

1
2
Octal representation is following \0342\0230\0240
?

以下是所有可用的Unicode emoji的列表:

https://en.wikipedia.org/wiki/emoji unicode_块

例子:

1
2
echo -e"\U1F304"
??

要获取此字符的ASCII值,请使用hexdump

1
2
3
4
echo -e"??" | hexdump -C

00000000  f0 9f 8c 84 0a                                    |.....|
00000005

然后使用十六进制格式通知的值

1
2
echo -e"\xF0\x9F\x8C\x84\x0A"
??


如果已知Unicode字符的十六进制值

1
2
H="2620"
printf"%b""\u$H"

如果已知Unicode字符的十进制值

1
2
3
declare -i U=2*4096+6*256+2*16
printf -vH"%x" $U              # convert to hex
printf"%b""\u$H"