自定义 Bash 主题
对于广为人知的 fish、Oh-My-Zsh,尽管他们内置了很多功能,但是实际上大部分人只用到了两部分:
- 漂亮的主题
- Tab 补全
在这里,我们针对主题部分进行自定义修改。
转义字符
对于终端中,输入命令前面的部分,可以使用PS1
环境变量设置。
如
export PS1="\u@\H:\w"
就是将前面的部分设置为[用户名]@[主机名]:[当前目录]
相应的转义字符可见表
符号 | 含义 | 示例 |
---|---|---|
\h |
当前主机名(第一部分) | mycomputer |
\H |
当前主机名(全称) | mycomputer.example |
\u |
当前用户名 | mario |
\v |
命令解释器 | bash |
\l |
终端 | ttys02 |
\w |
目录 | /usr/local/src |
\W |
文件夹 | src |
\A |
时间(HH:MM) | 14:23 |
\t |
时间(HH:MM:SS) | 14:23:52 |
\@ |
时间(HH:MM AM/PM) | 07:23 AM |
\T |
时间(12小时) | 02:23:52 |
\D |
日期 | Mon Feb 22 |
$? |
前一条命令退出状态 | 0 |
首先,限定下面的需求:
- 需要可以查看主机名和用户名,这样如果有多个服务器也不会混乱
- 需要可以看到绝对路径,这样可以确定及时存在同名目录也可以确保在正确的目录执行程序
- 带时间,可以记录一下每个命令结束时间(每一行的时间实际上是上一行结束时间)
可以使用
export PS1="\$ \t \u@\H:\w >"
颜色配置
上面虽然符合功能要求,但是并不“好看”,一个好看的主题,起码应该是有多种颜色的。
这里,可以使用 ANSI 颜色进行配置,使用\[\e[font;fg;bgm\]
可以对后面的文字输出进行样式管理
export PS1='$ \[\e[0;95;40m\]\t\[\e[0m\] \[\e[0;33;40m\]\u\[\e[0m\]@\[\e[0;96;40m\]\H\[\e[0m\]:\[\e[0;36;40m\]\w\[\e[0m\] \[\e[1;37;40m\]>\[\e[0m\] '
上面的部分看起来非常凌乱,但是如果分开看的话,还是可以对照下面的部分看懂的。
字体
font
为字体编码,包括加粗、斜体、闪烁等各种特效,具体编码见表
编码 | 含义 |
---|---|
1 | 加粗 |
2 | 半透明 |
3 | 斜体 |
4 | 下划线 |
5 | 闪烁 |
6 | 快速闪烁 |
7 | 反色 |
8 | 隐藏 |
9 | 删除线 |
前景色
fg
为前景色编码,根据具体环境不同,有三种格式可以选择:
- 基本颜色:
[颜色编码]
- 256 颜色:
38;5;[编码]
- rgb 颜色:
38;2;[红色];[绿色];[蓝色]
编码 | 含义 |
---|---|
30 | 黑色 |
31 | 红色 |
32 | 绿色 |
33 | 黄色 |
34 | 蓝色 |
35 | 品红 |
36 | 青色 |
37 | 白色 |
90 | 浅黑 |
91 | 浅红 |
92 | 浅绿 |
93 | 浅黄 |
94 | 浅蓝 |
95 | 淡紫 |
96 | 淡青 |
97 | 浅白 |
背景色
bg
为背景色编码,根据具体环境不同,有三种格式可以选择:
- 基本颜色:
[颜色编码]
- 256 颜色:
48;5;[编码]
- rgb 颜色:
48;2;[红色];[绿色];[蓝色]
编码 | 含义 |
---|---|
40 | 黑色 |
41 | 红色 |
42 | 绿色 |
43 | 黄色 |
44 | 蓝色 |
45 | 品红 |
46 | 青色 |
47 | 白色 |
100 | 浅黑 |
101 | 浅红 |
102 | 浅绿 |
103 | 浅黄 |
104 | 浅蓝 |
105 | 淡紫 |
106 | 淡青 |
107 | 浅白 |
带脚本的主题
如果需要更多的功能,则可以使用 bash 本身的功能来实现,如我们需要以下功能:
- 如果上一行成功执行,则开始的
$
为绿色,否则为红色 - 如果当前目录是一个 Git 项目,则显示当前的分支
首先,我们先不考虑PS1
,在 Bash 中如何实现上面的功能呢?
前者实际上只是一个判断,判断$?
是否为0
即可
if [ $? -eq 0 ]; then echo -e '\[\e[1;32;40m\]Success\[\e[0m\]'; else echo -e '\[\e[1;31;40m\]Fail\[\e[0m\]'; fi
这里\[
和\]
在 echo 中未被转义,不过并不影响结果。
而后者
branch=$(git status 2>/dev/null | head -n 1 | cut -d " " -f 3 2>/dev/null); \ if [[ -n ${branch} ]]; then echo -e "@\[\e[1;91m\]${branch}\[\e[0m\]"; fi
综上所述,如果PS1
的部分代码本身使用$()
或反引号包裹,那么将会实时执行,从而实现对动态内容的展示。
最后的代码合起来为
export PS1='$(if [ $? -eq 0 ]; then echo -ne "\[\e[1;32m\]$\[\e[0m\]"; else echo -ne "\[\e[1;31m\]$\[\e[0m\]"; fi) \[\e[95m\]\t\[\e[0m\] \[\e[33m\]\u\[\e[0m\]@\[\e[96m\]\H\[\e[0m\]:\[\e[36m\]\w\[\e[0m\]$(branch=$(git status 2>/dev/null | head -n 1 | cut -d " " -f 3 2>/dev/null);if [[ -n ${branch} ]]; then echo -ne "@\[\e[1;91m\]${branch}\[\e[0m\]";fi) \[\e[1;37m\]>\[\e[0m\] '
更优雅的生成方式
上述内容在使用上没有任何问题,但是在维护上,问题很大。更好的办法是使用脚本生成PS1
语句,这样会有更高的可维护性。
通过对各种颜色的显示进行封装,每部分单独处理,最后拼接到一起,这样可以使得项目维护性更高。
下面的代码可以在source
后,在当前终端配置PS1
,同时会输出一份可以复制的脚本,可以粘贴到.bashrc
中,以便后续自动生成
fg_color() { case "$1" in black) echo 30;; red) echo 31;; green) echo 32;; yellow) echo 33;; blue) echo 34;; magenta) echo 35;; cyan) echo 36;; white) echo 37;; lightBlack) echo 90;; lightRed) echo 91;; lightGreen) echo 92;; lightYellow) echo 93;; lightBlue) echo 94;; lightMagenta) echo 95;; lightCyan) echo 96;; lightWhite) echo 97;; orange) echo 38\;5\;166;; *) echo $1;; esac } bg_color() { case "$1" in black) echo 40;; red) echo 41;; green) echo 42;; yellow) echo 43;; blue) echo 44;; magenta) echo 45;; cyan) echo 46;; white) echo 47;; orange) echo 48\;5\;166;; lightBlack) echo 100;; lightRed) echo 101;; lightGreen) echo 102;; lightYellow) echo 103;; lightBlue) echo 104;; lightMagenta) echo 105;; lightCyan) echo 106;; lightWhite) echo 107;; *) echo $1;; esac; } text_effect() { case "$1" in reset) echo 0;; bold) echo 1;; weak) echo 2;; italic) echo 3;; underline) echo 4;; blink) echo 5;; quickBlink) echo 6;; reverse) echo 7;; hide) echo 8;; del) echo 9;; *) echo $1 esac } color() { font=$(text_effect $1) fg=$(fg_color $2) bg=$(bg_color $3) code="" first=0 for c in $font $fg $bg do if [[ $c -ne "" ]]; then if [[ $first -ne 0 ]]; then code+=";" fi code+="$c" first=1 fi done text=$4 echo "\[\e[${code}m\]${text}\[\e[0m\]" } # export PS1='`a=$?; if [ $a -eq 0 ]; then echo -ne "\[\e[32;1m\]\$\[\e[0m\]"; else echo -ne "\[\e[31;1m\]\$\[\e[0m\]"; fi` \[\e[35;1m\]\t\[\e[0m\] \[\e[1;41m\]▶\[\e[0m\] \[\e[1;41m\]\u@\H:\w\[\e[0m\] \[\e[1;41m\]▶\[\e[0m\]\$?' prefix_ok="$(color bold green "" \$)" prefix_fail="$(color bold red "" \$)" prefix='$(if [ $? -eq 0 ]; then echo -ne '"\"${prefix_ok}\""'; else echo -ne '"\"${prefix_fail}\""'; fi)' tm="$(color "" lightMagenta "" \\t)" host="$(color "" lightCyan "" \\H)" user="$(color "" yellow "" \\u)" dir="$(color "" cyan "" \\w)" git='$(branch=$(git status 2>/dev/null | head -n 1 | cut -d " " -f 3 2>/dev/null);if [[ -n ${branch} ]]; then echo -ne "@\[\e[1;91m\]${branch}\[\e[0m\]";fi)' P="${prefix} ${tm} ${user}@${host}:${dir}${git} $(color bold white "" \>) " echo "export PS1='${P}'" echo "export PS1='${P}'" > theme.bash export PS1=$P