上周六参加了好久没有参加的的 SHLUG 月聚,恰逢 TualatriX 也带团来上海。自由讨论的时候,我看到 TualatriX 的终端十分色彩斑斓,便询问,他给我们展示了他的 bash 的两个特色功能:1、当上一条命令返回结果不为0时显示返回值并高亮显示提示符;2、自动检测git分支。他说这个在他的博客上都可以找到,今天想起来去找了一下,发现了这篇:史上最强的PS1 | I’m TualatriX,感觉满强大的。
不过,说实话,我觉的这个还不够完美,原因有二:一是我发觉高亮显示的时候那个配色相当不怎么样,二是我本来就讨厌提示符太长,这样一下就更长了……于是我就想起 ghosTM 的 zsh 里面有一些信息是放在右边的,我想把返回值也扔右边去,并且是右边上移一行。此外,由于很少使用 git
,所以检测 git
分支的功能也就不需要了~
先放一个最终效果图:
然后直接写出了我的新的 PS1
:
PS1='`a=$?;if [ $a -ne 0 ]; then a=" "$a; echo -ne "\[\e[s\e[1A\e[$((COLUMNS-2))G\e[31m\e[1;41m${a:(-3)}\e[u\]\[\e[0m\e[7m\e[2m\]"; fi`\[\e[1;32m\]\u@\h:\[\e[0m\e[1;34m\]\W\[\e[1;34m\]\$ \[\e[0m\]'
非常复杂唉……让我自己再看一次都头晕……
分解这个提示符
上面看到这个 PS1
写的非常之复杂,不过其实拆解开来也没什么了不起的,只不过看起来蛋疼罢了~
这个 PS1
可以分为两个部分,第一个部分是:
`a=$?;if [ $a -ne 0 ]; then a=" "$a; echo -ne "\[\e[s\e[1A\e[$((COLUMNS-2))G\e[31m\e[1;41m${a:(-3)}\e[u\]\[\e[0m\e[7m\e[2m\]"; fi`
第二个部分是:
\[\e[1;32m\]\u@\h:\[\e[0m\e[1;34m\]\W\[\e[1;34m\]\$ \[\e[0m\]
我们先来研究第二部份,这个部分看起来比较简短。其中我们可以看到一个 PS1
里面非常基本的结构:\u@\h:\W\$
,这个结构在我的电脑里就显示为 upsuper@upsuper-laptop:~$
大家大概可以猜到里面是什么意思了吧。
这个基本骨架理出来,剩下的是看过去最蛋疼的那堆莫名其妙的符号了~我们看到很多 \e[ 这样的东西,事实上这个叫做 ANSI 控制码,在 Linux 和 Windows 的命令行里面都是通用的,\e
是 Escape 键的键码,\e[
是一切 ANSI 控制码的开头。首先来到 \e[1;32m
这个控制码,这表示设置这个符号之后的字符为亮绿色,而 \e[0m
则是清除所有格式,这样看有没有一点清晰了呢?更多用法可以参考维基百科条目ANSI escape code。
之后还有两个东西不清楚,就是 \[
和 \]
,这两个并不是 ANSI 控制码,而是 Bash 提供的转义符。他们的解释说实话我没看太懂,不过我的理解大概就是,夹在 \[
和 \]
之间的部分 Bash 假定他们的宽度为0,不正确地标注这两个符号会导致 Bash 的换行错误。总之在所有控制符两侧都加上这两个就对了~
第二个部分解决了,下面来看蛋疼的第一部份
`a=$?;if [ $a -ne 0 ]; then a=" "$a; echo -ne "\[\e[s\e[1A\e[$((COLUMNS-2))G\e[31m\e[1;41m${a:(-3)}\e[u\]\[\e[0m\e[7m\e[2m\]"; fi`
很明显,整个结构被一个正引号引起来,表示执行并返回其中的结果。这样我们就可以把这个部分分解开来了:
a=$?
if [ $a -ne 0 ]; then
a=" "$a
echo -ne "\[\e[s\e[1A\e[$((COLUMNS-2))G\e[31m\e[1;41m${a:(-3)}\e[u\]\[\e[0m\e[7m\e[2m\]"
fi
稍微懂点编程就会觉得这也没什么技术含量嘛,其中 $?
就是上一个程序运行的返回值,我们获取并判断他,如果不为零就进行下面的操作。a=" "$a
这句我们待会而再看,先看下面那个 echo -ne
的语句。echo
我们知道是显示字符串,而 -ne
实际上是两个参数 -n
和 -e
,-n
表示输出字符串后不输出换行符,-e
表示解析后面的转义符。
最后就剩分析那个打印的东西了。我们发现主要部分其实和上面是一样的,无非就是一些设置格式的事情,我们去掉格式设置,发现主要是这样的:
\e[s\e[1A\e[$((COLUMNS-2))G${a:(-3)}\e[u
这个部分仍然包含许多 ANSI 控制符,第一个是 \e[s
,表示保存当前光标位置,与最后一个表示恢复光标位置的控制符 \e[u
遥相呼应,由于我们需要大规模移动光标,所以我们要备份一下位置。然后我们看到 \e[1A
,这个控制符表示将光标上移一行。然后之后有一个很复杂的东西 \e[$((COLUMNS-2))G
,这个对应的控制符是 \e[*G
,表示设置光标到第几列,而 $((COLUMNS-2))
表示这个列数为当前可显示的最大列数-2。后面有一个 ${a:(-3)}
,也就是取前面的后三位显示(返回值的范围是0-255)。
现在我们回到前面的 a=" "$a
,发现这个的目的其实是和 ${a:(-3)}
对应,让这个部分无论如何保证有三个字符可以出现。事实上最初我并不是这么写的,而是写 $((COLUMNS-${#a}+1))
,表示 $a
有多长就显示多长。但这样感觉不美观,就改成了固定3字符长。
到这里也就结束了,然后我们发现,其实看过去很复杂的东西,拆开来还是挺简单的嘛~
参考资料
There are comments.