在本章中,我们将介绍一个看似微不足道的细节——我们的shell提示符。本次检查将揭示shell和终端仿真器程序的一些内部工作原理。
与Linux中的许多东西一样,shell提示符是高度可配置的,虽然我们几乎认为它是理所当然的,但一旦我们学会如何控制它,提示符就是一个非常有用的设备。
我们的默认提示符看起来像这样:
xxxxxxxxxx
[me@linuxbox ~]$
请注意,它包含我们的用户名、主机名和当前工作目录,但它是如何实现的呢?事实证明,这很简单。提示由名为 PS1
(“prompt string 1(提示字符串1)”的缩写)的环境变量定义。我们可以使用 echo
命令查看 PS1
的内容:
xxxxxxxxxx
[me@linuxbox ~]$ echo $PS1
[\u@\h \W]\$
注意:如果你的结果与上面的例子不一样,不要担心。每个Linux发行版对提示字符串的定义都有点不同,有些非常奇特。
从结果中,我们可以看到 PS1
包含我们在提示符中看到的一些字符,如括号、@符号和美元符号,但其余的都是个谜。我们中精明的人会认出这些是反斜杠转义的特殊字符,就像我们在【第7章.以Shell的视角看待世界】中看到的那样。下表提供了bash在提示字符串中特别处理的字符的部分列表:
序列 | 显示的值 |
---|---|
\a | ASCII铃声。这会使计算机在遇到时发出蜂鸣声。 |
\d | 以day、month、date格式表示的当前日期。例如,“Mon May 26”。 |
\h | 本地计算机的主机名减去尾随域名。 |
\H | 完整的主机名。 |
\j | 当前shell会话中运行的作业数。 |
\l | 当前终端设备的名称。 |
\n | 换行符。 |
\r | 回车。 |
\s | shell程序的名称。 |
\t | 当前时间,采用24小时格式,小时:分钟:秒。 |
\T | 当前时间,采用12小时格式。 |
\@ | 当前时间,采用AM/PM的12小时格式。 |
\A | 当前时间,采用24小时格式,小时:分钟。 |
\u | 当前用户的用户名。 |
\v | shell的版本号。 |
\V | shell的版本和发布号。 |
\w | 当前工作目录的名称。 |
\W | 当前工作目录名称的最后一部分。 |
\! | 当前命令的历史编号。 |
\# | 在此shell会话期间输入的命令数。 |
\$ | 除非我们拥有超级用户权限,否则将显示“$”字符。 在这种情况下,它会显示一个“#”。 |
\[ | 表示一系列一个或多个非打印字符的开始。 这用于嵌入非打印控制字符,以某种方式操纵终端模拟器,例如移动光标或更改文本颜色。 |
\] | 表示非打印字符序列的结束。 |
使用此特殊字符列表,我们可以更改提示以查看效果。首先,我们将备份现有的提示字符串,以便稍后还原。为此,我们将把现有的字符串复制到我们自己创建的另一个shell变量中。
xxxxxxxxxx
[me@linuxbox ~]$ ps1_old="$PS1"
我们创建了一个名为 ps1_old
的新变量,并将 ps1
的值赋给它。我们可以使用 echo
命令验证字符串是否已被复制:
xxxxxxxxxx
[me@linuxbox ~]$ echo $ps1_old
[\u@\h \W]\$
我们可以在终端会话期间的任何时候通过简单地反转过程来恢复原始提示:
xxxxxxxxxx
[me@linuxbox ~]$ PS1="$ps1_old"
现在我们已经准备好继续,让我们看看如果我们有一个空的提示字符串会发生什么:
xxxxxxxxxx
[me@linuxbox ~]$ PS1=
如果我们不对提示字符串赋值,则什么也得不到。根本没有提示字符串!提示仍然存在,但没有显示任何内容,正如我们要求它做的那样。由于这看起来有点令人不安,我们将用最小提示替换它。
xxxxxxxxxx
PS1="\$ "
好多了。至少现在我们可以看到我们在做什么。请注意双引号内的尾随空格。这在显示提示时提供了美元符号和光标之间的空间。
让我们在提示中添加一个铃声:
xxxxxxxxxx
$ PS1="\[\a\]\$ "
现在,每次显示提示时,我们都应该听到一声蜂鸣声,尽管有些系统禁用了此“功能”。这可能会很烦人,但如果我们需要在执行特别长时间运行的命令时收到通知,这可能会有用。请注意,我们包含了 \[
和 \]
序列。由于ASCII铃( \a
)不会“打印”,也就是说,它不会移动光标,我们需要告诉bash,这样它才能正确地确定提示的长度。
接下来,让我们尝试用一些主机名和一天中的时间信息制作一个信息提示。
xxxxxxxxxx
$ PS1="\A \h \$ "
17:33 linuxbox $
如果我们需要跟踪执行某些任务的时间,在提示中添加时间将非常有用。最后,我们将创建一个与原始提示类似的新提示:
xxxxxxxxxx
17:37 linuxbox $ PS1="<\u@\h \W>\$ "
<me@linuxbox ~>$
尝试上表中列出的其他序列,看看你是否能想出一个出色的新提示。
大多数终端模拟器程序对某些非打印字符序列做出响应,以控制字符属性(如颜色、粗体文本和可怕的闪烁文本)和光标位置。我们将稍微介绍一下光标位置,但首先我们将看看颜色。
在古代,当终端连接到远程计算机时,有许多相互竞争的终端品牌,它们的工作方式都不同。他们有不同的键盘,他们都有不同的方式来解释控制信息。Unix和类Unix系统有两个相当复杂的子系统来处理终端控制的问题(称为 termcap
和 terminfo
)。如果您查看终端模拟器设置的最深处,您可能会找到终端模拟类型的设置。
为了使终端讲某种通用语言,美国国家标准协会(ANSI)开发了一套标准的字符序列来控制视频终端。老DOS用户会记得用于解释这些代码的 ANSI.SYS 文件。
通过向终端仿真器发送嵌入在要显示的字符流中的ANSI转义码(ANSI escape code)来控制字符颜色。控制代码未在显示器上“打印出来”;相反,它被终端解释为指令。如上表所示, \[
和 \]
序列用于封装非打印字符。ANSI转义码以八进制033(Esc 键生成的代码)开头,后跟可选字符属性,后跟指令。例如,将文本颜色设置为正常(属性=0)的代码,黑色文本如下:
xxxxxxxxxx
\033[0;30m
下表列出了可用的文本颜色。请注意,颜色分为两组,通过应用粗体字符属性(1)进行区分,该属性创建了“light(浅色)”的外观。
序列 | 文字颜色 | 序列 | 文字颜色 |
---|---|---|---|
\033[0;30m | Black,黑 | \033[1;30m | Dark gray,深灰色 |
\033[0;31m | Rad,红 | \033[1;31m | Light 红,浅红 |
\033[0;32m | Green,绿 | \033[1;32m | Light green,浅绿 |
\033[0;33m | Brown,棕 | \033[1;33m | Yellow,黄 |
\033[0;34m | Blue,蓝 | \033[1;34m | Light blue,浅蓝 |
\033[0;35m | Purple,紫 | \033[1;35m | Light purple,浅紫 |
\033[0;36m | Cyan,青 | \033[1;36m | Light cyan,浅青 |
\033[0;37m | Light gray,浅灰 | \033[1;37m | White,白 |
让我们试着做一个红色提示。我们将在开头插入转义码:
xxxxxxxxxx
<me@linuxbox ~>$ PS1="\[\033[0;31m\]<\u@\h \W>\$ "
<me@linuxbox ~>$
这是有效的,但请注意,我们在提示后键入的所有文本也将显示为红色。为了解决这个问题,我们将在提示的末尾添加另一个转义码,告诉终端模拟器返回到以前的颜色:
xxxxxxxxxx
<me@linuxbox ~>$ PS1="\[\033[0;31m\]<\u@\h \W>\$\[\033[0m\] "<me@linuxbox ~>$
好多了!
也可以使用下表中列出的代码设置文本背景颜色。背景颜色不支持粗体属性:
序列 | 背景色 | 序列 | 背景色 |
---|---|---|---|
\033[0;40m | Black,黑 | \033[0;44m | Blue,蓝 |
\033[0;41m | Rad,红 | \033[0;45m | Purple,紫 |
\033[0;42m | Green, 绿 | \033[0;46m | Cyan,青 |
\033[0;43m | Brown,棕 | \033[0;47m | Light gray,浅灰 |
我们可以通过对第一个转义代码进行简单的更改来创建一个红色背景的提示。
xxxxxxxxxx
<me@linuxbox ~>$ PS1="\[\033[0;41m\]<\u@\h \W>\$\[\033[0m\] "<me@linuxbox ~>$
试试颜色代码,看看你能创造什么!
注意:除了 normai(0)
(正常)和 bold(1)
(粗体)字符属性外,文本还可以具有 underscore(4)
(下划线)、 blinking(5)
(闪烁)和 inverse(7)
(反转)属性。然而,为了保持良好的品味,许多终端模拟器拒绝尊重闪烁属性。
转义码可用于定位光标。这通常用于在屏幕上的不同位置(例如每次绘制提示时在上角)提供时钟或其他类型的信息。下表列出了定位光标的转义码:
转义码 | 动作 |
---|---|
\033[l;cH | 将光标移动到第 l 行和第 c 列 |
\033[nA | 将光标向上移动n行 |
\033[nB | 将光标向下移动n行 |
\033[nC | 将光标向前移动n个字符 |
\033[nD | 将光标向回移动n个字符 |
\033[2J | 清除屏幕并将光标移动到左上角(第0行第0列) |
\033[K | 从光标位置清除到当前行的末尾 |
\033[s | 存储当前光标位置 |
\033[u | 回忆存储的光标位置 |
使用上表中的代码,我们将构造一个提示,每次显示提示时,在屏幕顶部绘制一个红色条,其中包含一个时钟(以黄色文本呈现)。提示符的代码是一个看起来很可怕的字符串:
xxxxxxxxxx
PS1="\[\033[s\033[0;0H\033[0;41m\033[K\033[1;33m\t\033[0m\033[u\]<\u@\h \W>\$ "
下表概述了字符串的每个部分的作用:
转义码 | 动作 |
---|---|
\[ | 开始非打印字符序列。这样做的目的是允许bash正确计算可见提示的大小。没有精确的计算,命令行编辑功能无法正确定位光标。 |
\033[s | 存储光标位置。在屏幕顶部绘制条形图和时钟后,需要返回提示位置。请注意,一些终端模拟器无法识别此代码。 |
\033[0;0H | 将光标移动到左上角,即第0行第0列。 |
\033[0;41m | 将背景颜色设置为红色。 |
\033[K | 从当前光标位置(左上角)清除到行的末尾。由于背景颜色现在是红色,因此行被清除为该颜色,从而创建了我们的条形图。请注意,清除到行的末尾不会改变光标的位置,光标仍位于左上角。 |
\033[1;33m | 将文本颜色设置为黄色。 |
\t | 显示当前时间。虽然这是一个“打印”元素,但我们仍然将其包含在提示符的非打印部分,因为我们不希望bash在计算显示的提示符的真实大小时包含时钟。 |
\033[0m | 关闭颜色。这会影响文本和背景。 |
\033[u | 恢复之前保存的光标位置。 |
\] | 结束非打印字符序列。 |
<\u@\h \W>\$ | 提示字符串。 |
显然,我们不想一直在输入那个怪物,所以我们想把提示符存储在某个地方。我们可以通过将提示添加到 .bashrc 文件中来使其永久化。为此,请将以下两行添加到文件中:
xxxxxxxxxx
PS1="\[\033[s\033[0;0H\033[0;41m\033[K\033[1;33m\t\033[0m\033[u\]<\u@\h \W>\$ "
export PS1
信不信由你,使用涉及shell函数和脚本的提示可以做更多的事情,我们在这里没有介绍,但这是一个很好的开始。并不是每个人都会足够关心更改提示,因为默认提示通常是令人满意的。但对于我们这些喜欢修补的人来说,shell提供了几个小时休闲娱乐的机会。