老师!我想知道!如果只使用原生的终端机要怎麽客制化 Prompt 呢!

这篇文章是来自同事的许愿,到底能不能不要安装那些 iTerm2、zsh、oh-my-zsh、字型等等,只用原本的终端机还有 bash 就做到差不多的效果呢?

答案是可以的喔!透过写一些简单的设定到 .bashrc 档案,就可以让我们的 prompt 显示你想显示的内容:例如你想显示自己的名字、想要显示时间、上一个指令的状态等等,也可以帮他们上色,如果想要有很多可爱的小图示或是想要右边也显示一些资讯,只依靠 bash、并且不另外安装字型的话,可能就比较困难、麻烦一点,所以这里我们就只介绍怎麽简单透过设定 .bashrc 来制作自己喜欢的 prompt 吧!


我们先来理解一下 .bashrc 还有 .bash_profile 有什麽分别,当我们打开终端机、或是像是利用 ssh 指令从远端连线进来到系统、或是执行一份 shell script 时,都会去触发 bash 的启动,当启动时,他会在系统下去寻找一些特定的档案,并执行档案的内容,类似为启动後做一些初始化的设定,而 bash 会去寻找哪些档案,则是根据启动了什麽类型的 shell 而定。

Shell 可以是 interactive 或是 non-interactive,按照字面上的解释就是互动式还有非互动式。互动式的 shell 就像一般我们使用终端机那样,他可以从终端机读取使用者的输入、也可以输出一些讯息到使用者的终端机,非互动式的就是像执行一个 script file 一样。

一个互动式的 shell 又可以分做 login shell 及 non-login shell,login shell 是使用者不管透过远端连线或是本地端打开的方式登入终端机都算是,而 non-login shell 是透过 login shell 所触发,像是我们在终端机里面下指令 bash 会进入 bash 的环境、或是像我们在终端机打开一个 Gnome tab 一样。

我们每次打开终端机的时候,是不是最上面都会显示 Last login ...,我们打开一个终端机,他就是一个 interactive login shell 哦!

若是平常我们直接下指令 bash,会进入 bash,这时你会发现你的 prompt 变成 bash-3.2,你可以直接在这个状态下执行任何 bash 的指令,如果要离开,则是使用指令 exit 就可以又回到原本的 prompt 了。

当一个 interactive login shell 被打开後, bash 会先在 /etc 路径底下找寻名为 profile 的档案,如果档案存在,则 bash 会去执行档案内的指令,不管 /etc/profile 存不存在,接着都会继续以下的步骤, bash 会接着在家目录底下依照顺序寻找 .bash_profile.bash_login 还有 .profile 这三个档案,但只会执行第一个找到、而且要是可读的那个档案里的指令。

那如果是 interactive non-login shell 的话,那 bash 会去寻找家目录下有没有一个可读的 .bashrc 档案,若有的话就去执行里面的指令。

一般来说,我们可以把只需要被执行一次的指令放在 .bash_profile 里面,像是设定 $PATH 环境变数,那如果是每一次打开一个 shell 的时候都需要被执行的,像是客制化 prompt 、一些设定别名的指令,如: alias,我们就可以写在 .bashrc 里面。

另外我们也可以在 .bash_profile 里面加上以下这段指令,这样就可以确保每一次登入到终端机时,.bash_profile 还有 .bashrc 都会被执行到了:

 if [ -f ~/.bashrc ]; then
     . ~/.bashrc
 fi

-f 是用来判断家目录底下是否有 .bashrc 这个档案存在,并且要是 regular file。
~/ 是家目录路径底下,regular file 则是像一般的文字档、图片啊、可执行档等等,如果对档案类型有兴趣想了解的话,可以另外自己阅读文章来了解一下哦!

以上参考文章 .bashrc vs .bash_profile

档案类型的参考:Regular file


写了那麽多,终於可以开始介绍怎麽客制化你想要的 prompt 了!

主要我们是透过在 .bashrc 档案里面修改 PS1 的这个环境变数,来达到修改 Prompt 样貌的结果,除了 PS1 还有 PS2 PS3 PS4 ,分别可以设定不同的部分,但我们这次只关注 PS1 的设定,对其他三个有兴趣的人可以自己另外查查资料喔!

设定 PS1 主要是透过一些原先 bash 就定义好的参数,可以查看 bash 的说明,使用指令 man bash,并看 PROMPTING 的部分。

以下列出一些比较常使用的:

  1. \d 显示日期,格式: Weekday Month Date
  2. \D{format} 会依照给定的 format 来显示时间,这里的格式是要 strftime(3) 可以判读的格式。 -> 参考: strftime(3) — Linux manual page
  3. \h 显示简短 hostname,显示到第一个 . 之前。
  4. \H 显示完整 hostname。
  5. \t 显示时间,以 24 小时制显示 HH:MM:SS 格式。 (如: 18:46:27 )
  6. \T 显示时间,以 12 小时制显示 HH:MM:SS 格式。 (如: 06:46:27 )
  7. \@ 显示时间,以 12 小时制,并以 AM / PM 方式显示 HH:MM 格式。 (如: 06:46 下午)
  8. \A 显示时间,以 24 小时制显示 HH:MM 格式。 (如: 18:46 )
  9. \u 显示目前使用者的名字。
  10. \w 显示目前完整的工作路径,如果在家目录下,则以 ~ 显示。
  11. \W 显示简短工作路径,也就是显示目前所在的路径的最後一层,如果在家目录底下,一样是以 ~ 显示。
  12. \e 是 ASCII 跳脱字元,後面我们要来设定颜色时会用到。

接下来就是按照自己喜好来做排列组合罗,例如我想要显示的样子是:目前时间 Chrissspy 目前所在路径 $ ,那我们就可以把 PS1 设定成:\A Chrissspy \W $,显示出来的效果是:

因此你就可以善用各种文字、符号、以及上面的参数,来表达出你喜欢的 Prompt!


但即使我们能够客制出我们想要的样子了,还是缺少一点色彩让我们更容易辨识,不晓得大家有没有在类似 ptt 的地方发过文呢?以前我也曾在其他 BBS 论坛发过文,如果你要加上颜色的话,就要用 ANSI escape code 来设定,如果要设定 PS1 文字的颜色也是一样的方式喔!

先来看这张 256 个颜色的数字对照表,取自维基百科 Colors 8-bits 的部分

接下来就来介绍要怎麽透过 ANSI escape code 来设定自己想要的颜色吧!

  1. 设定前景,也就是文字的颜色:\e[38;5;nm 其中 n 要换成自己想要的颜色的数字。
  2. 设定背景,也就是文字会有个底色: \e[48;5;km 其中 k 也是替换成自己想要的颜色的数字。
  3. 如果同时要设定前景和背景,可以写: \e[38;5;nm;48;5;km
  4. 结束目前设定: \e[m

这个结束设定的 \e[m,举个例子来说明他的用处:比如说我们今天有一串文字 Apple,A 想用红色,第二个 p 用黄色,其余字母都不设定颜色,那麽你可能会想写这样:

但出来的结果却是:

怎麽会是前两个字都是红色,後面三个字都是黄色呢?那就是因为我们没有加上结束目前设定的 \e[m,所以颜色就会套用文字直到下一个颜色设定出现,所以要达成我们刚刚的需求,正确写法应该是:

出来的结果就会如同我们预期的罗:


讲到这里,来给大家一个范例吧!如果我想要我的 prompt 设定成 目前时间 Chrissspy 目前所在路径 $,并且时间的部分要白色底、黑色文字,而我的名字 Chrissspy 要天空蓝色为底、文字为深蓝色,目前所在路径是粉红色底、白色文字,那该怎麽写呢?

先写出文字的部分: \A Chrissspy \W $,接着我们一个部分一个部份加上想要的颜色:

  1. 时间的部分:白色底黑色文字 -> \e[38;5;0;48;5;15m\A\e[m
  2. Chrissspy 部分:天空蓝为底色、文字为深蓝色 -> \e[38;5;18;48;5;159mChrissspy\e[m
  3. 目前所在路径: 粉红色底、白色文字 -> \e[38;5;15;48;5;211m\W\e[m
  4. 整个组合起来就是 \e[38;5;0;48;5;15m\A\e[m\e[38;5;18;48;5;159m Chrissspy\e[m\e[38;5;15;48;5;211m \W\e[m $

出来的结果是:

看起来怪怪的吧,那是因为空格也会被设定上背景颜色喔,那麽来调整一下,把空格都移到颜色设定外面,变成: \e[38;5;0;48;5;15m\A\e[m \e[38;5;18;48;5;159mChrissspy\e[m \e[38;5;15;48;5;211m\W\e[m $

结果会变成:

可以善用一些特殊符号,例如三角形,还有空格的组合,可以配合出漂亮又易懂的 prompt,像是:
\e[38;5;0;48;5;15m\A ▶ \e[m\e[38;5;18;48;5;159m Chrissspy ▶ \e[m\e[38;5;15;48;5;211m \W \e[m $

出来的结果是:

-> 特殊符号参考


以上的设定,我们只要在家目录底下打开一个档名为 .bashrc 的档案,并在里面放入这一行指令 export PS1="xxxxx",其中 xxx 要替换成你自己设计好的 prompt,以我们刚刚设计好的为例:
就是在档案里加上:

 export PS1="\e[38;5;0;48;5;15m\A ▶ \e[m\e[38;5;18;48;5;159m Chrissspy ▶ \e[m\e[38;5;15;48;5;211m \W \e[m $ "

储存档案後,在终端机家目录下指令 . ~/.bashrc,你就可以看到你的 prompt 变成你想要的样子罗!

如果你发现你每次打开终端机,你的 .bashrc 好像都没有被载入,那你可以先看看你的家目录底下有没有 .bash_profile 这个档案,如果有就编辑他,没有的话就开一个档案,名字是 .bash_profile,并在里面加上指令如下:

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

指令加上去之後储存档案,并结束终端机,之後每次打开终端机,你应该都可以看见你的 .bashrc 内的设定都有被执行到罗!


最後,如果你想要加上一些跟 git 相关的资讯到 prompt 里面,像是分支资讯、还有 git 目前状态的话,网路上有许多人写好了 function 可以去得到 git 的相关资讯,你只要在 .bashrc 档案的开头加上以下的程序码(放在本篇教学的最後),最後在 PS1 里设定时,写法是

\`parse_git_branch\`

如果我把这一段加到我刚刚设定好的 prompt 里面,我的 PS1 会变成:

export PS1="\e[38;5;0;48;5;15m\A ▶ \e[m\e[38;5;18;48;5;159m Chrissspy ▶ \e[m\e[38;5;15;48;5;211m \W \e[m (\`parse_git_branch\`) $ "

出来的效果是:

一样可以加上颜色效果,就放在

\`parse_git_branch\`

的前後。

以下为要加在 .bashrc 的程序码中的一部分,如果你想要修改不同状态显示的符号的话,像是你想修改目前目录如果有 untracked file 时,原本用以下的程序码会显示 ?,如果你想改成显示 untracked?,就修改底下的这个部分里的 ?

if [ "${untracked}" == "0" ]; then
	bits="?${bits}"
fi

改成

if [ "${untracked}" == "0" ]; then
	bits="untracked?${bits}"
fi

出来的效果会如下图,又有惊叹号又显示 untracked? 是表示目前工作路径底下的 git 的档案状态是同时有 dirty 和 untracked 的档案:


以下是如果你想显示 git 资讯可以使用的程序码:

# get current branch in git repo
function parse_git_branch() {
	BRANCH=`git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'`
	if [ ! "${BRANCH}" == "" ]
	then
		STAT=`parse_git_dirty`
		echo "[${BRANCH}${STAT}]"
	else
		echo ""
	fi
}

# get current status of git repo
function parse_git_dirty {
	status=`git status 2>&1 | tee`
	dirty=`echo -n "${status}" 2> /dev/null | grep "modified:" &> /dev/null; echo "$?"`
	untracked=`echo -n "${status}" 2> /dev/null | grep "Untracked files" &> /dev/null; echo "$?"`
	ahead=`echo -n "${status}" 2> /dev/null | grep "Your branch is ahead of" &> /dev/null; echo "$?"`
	newfile=`echo -n "${status}" 2> /dev/null | grep "new file:" &> /dev/null; echo "$?"`
	renamed=`echo -n "${status}" 2> /dev/null | grep "renamed:" &> /dev/null; echo "$?"`
	deleted=`echo -n "${status}" 2> /dev/null | grep "deleted:" &> /dev/null; echo "$?"`
	bits=''
	if [ "${renamed}" == "0" ]; then
		bits=">${bits}"
	fi
	if [ "${ahead}" == "0" ]; then
		bits="*${bits}"
	fi
	if [ "${newfile}" == "0" ]; then
		bits="+${bits}"
	fi
	if [ "${untracked}" == "0" ]; then
		bits="?${bits}"
	fi
	if [ "${deleted}" == "0" ]; then
		bits="x${bits}"
	fi
	if [ "${dirty}" == "0" ]; then
		bits="!${bits}"
	fi
	if [ ! "${bits}" == "" ]; then
		echo " ${bits}"
	else
		echo ""
	fi
}

如果你实在懒得研究要怎麽设定自己想要的样子要怎麽转换成文字设定到 PS1 ,那你可以参考这个网站:
Easy Bash Prompt Generator

可以让你透过简单拖拉、设定颜色,帮你产出一串 PS1 的设定喔!

以上是今天的分享,如果有错误再麻烦大家留言告诉我哦,感谢大家!希望大家喜欢今天的分享!


<<:  [Day 31] - 手把手跨出第一步!将Arduino打造成JavaScript的环境-Part 1

>>:  Flutter学习Day5 Widget StatelessWidget => StatefulWidget 实作

Day15 - 【概念篇】OAuth flows: Authorization Code

本系列文之後也会置於个人网站 +----------+ | Resource | | Owner ...

DAY1 揭开序幕与 MongoDB 简介

DAY1 揭开序幕与 MongoDB 简介 前言 终於鼓起勇气要报名 iThome 铁人赛! 本系列...

Day 38 (PHP)

1.除错查看var_dump、echo gettype,看变数有没有错 echo gettype($...

[Day 49] 留言板後台及前台(五) - 将留言板内容写进资料库

我们昨天做了错误处理, 今天才正式来处理写进资料库的内容, 我们先来做个范例, (我们不讨论CKEd...

Day 30 - 到客户端执行弱点扫瞄并修复的心得分享 第十七天

今天是专案修补的第17天,也是铁人赛的最後一天了。 这次把剩下的低风险弱点修补完这个案子就提前结束了...