跳转至内容

Bash/Prompt 自定义

来自 ArchWiki

Bash 有几个提示符可以自定义,以提高生产力、美观度和“技术宅”分数。

提示符

Bash 有五个可以自定义的提示符字符串

  • PS0 在每个命令之后、任何输出之前显示。
  • PS1 是主提示符,在每个命令之前显示,因此它是大多数人自定义的提示符。
  • PS2 是次提示符,当命令需要更多输入时(例如多行命令)显示。
  • PS3 不太常用。它是 Bash 的 select 内建命令显示的提示符,用于显示交互式菜单。与其他提示符不同,它不会扩展 Bash 转义序列。通常,您会在使用 select 的脚本中自定义它,而不是在您的 .bashrc 中。
  • PS4 也不常用。它在调试 bash 脚本时显示,以指示间接层级。第一个字符会重复以指示更深的层级。

所有提示符都是通过将相应变量设置为所需字符串来定制的(通常在 ~/.bashrc 中),例如:

PS2='> '

技术

虽然您可以简单地将提示符设置为一个纯文本字符串,但还有多种技术可以使提示符更具动态性和实用性。

Bash 转义序列

在打印提示符字符串时,Bash 会查找特定的反斜杠转义字符,并将其扩展为特殊字符串。例如,\u 会扩展为当前用户名,\A 会扩展为当前时间。因此,PS1 为 '\A \u $ ' 会显示为 17:35 用户名 $

有关转义序列的完整列表,请参阅 man 手册页 bash(1) § PROMPTINGBash 参考手册

Terminfo 转义序列

除了 Bash 识别的转义字符外,大多数终端还识别特殊转义序列,这些序列会影响终端本身,而不是打印字符。例如,它们可以更改后续打印字符的颜色、将光标移动到任意位置或清除屏幕。这些转义序列可能有些难以辨认,并且可能因终端而异,因此它们记录在 terminfo 数据库中。要查看您的终端支持哪些功能,请运行:

$ infocmp

功能名称(等号前的部分)可以在 terminfo(5) 中查找,以了解其作用。例如,setaf 会设置之后打印的文本的前景色。要获取功能的转义码,可以使用 tput 命令。例如:

$ tput setaf 2

打印设置前景色为绿色的转义序列。

注意 如果 tput 命令对您不起作用,请确保您已为 shell 设置了正确的 TERM 值。例如,如果您设置了 xterm 而不是 xterm-256colortput setaf 将只能使用颜色编号 0-7。

要将这些功能实际集成到您的提示符中,您可以使用 Bash 的命令替换和字符串插值。例如:

GREEN="\[$(tput setaf 2)\]"
RESET="\[$(tput sgr0)\]"

PS1="${GREEN}my prompt${RESET}> "
我的提示符>
注意 Bash man 手册页建议将 tput 输出包装在 \[ \] 中。这有助于 Bash 忽略不可打印字符,从而正确计算提示符的大小。这种包装在命令替换中无效,在这种情况下,必须使用 原始的 \1 \2

ANSI 转义序列

不幸的是,您的终端 terminfo 数据库中可能缺少有效的 ANSI 转义序列。对于较新功能(如 256 色支持)的转义序列,这种情况尤其普遍。在这种情况下,您不能使用 tput,必须手动输入转义序列。

请参阅 Wikipedia:ANSI escape code 获取转义序列的示例。每个转义序列都以一个字面上的转义字符开头,您可以使用 Bash 的转义序列 \e 输入。例如,\e[48;5;209m 会将背景设置为桃红色(如果您支持 256 色),\e[2;2H 将光标移动到屏幕的左上角附近。

在 Bash 转义序列不受支持的情况下(例如 PS3),您可以使用 Bash 的 printf 内建命令获取字面上的转义字符:

ESC=$(printf "\e")
PEACH="$ESC[48;5;209m"

嵌入命令

如果您想将某个命令的输出添加到您的提示符中,您可能会尝试使用命令替换。例如,要将可用内存量添加到您的提示符中,您可以尝试:

PS1="$(awk '/MemFree/{print $2}' /proc/meminfo) prompt > "
53718 prompt >
53718 prompt >
53718 prompt >

但这不起作用;显示的内存量每次都一样!这是因为该命令在 PS1 首次设置时只运行一次,之后再也不会运行。诀窍是简单地阻止替换,方法是转义 $ 或在单引号中定义它——无论哪种方式,它都将在提示符实际显示时进行替换:

PS1="\$(awk '/MemFree/{print \$2}' /proc/meminfo) prompt > "
# or
PS1='$(awk "/MemFree/{print \$2}" /proc/meminfo) prompt > '

为了防止长命令使您的 PS1 变得过大,您可以定义函数:

free_mem()
{
    awk '/MemFree/{print $2}' /proc/meminfo
}

PS1='$(free_mem) prompt > '
注意 您可以在替换函数中使用 terminfo/ANSI 转义序列,但 **不能** 使用 Bash 转义序列。特别是 \[ \] 不适用于包含不可打印字符的包装。您可以使用八进制转义符 \001\002(例如使用 printfecho -e)。

PROMPT_COMMAND

如果设置了 PROMPT_COMMAND 变量或数组,它将在 PS1 显示之前立即进行评估。这可以用来实现相当强大的效果。例如,它可以根据某个条件重新分配 PS1,或者在每次运行命令时对 Bash 历史记录执行某些操作。

注意 PROMPT_COMMAND 通常不应直接用于向提示符打印字符。在 PS1 之外打印的字符不会被 Bash 计算,这会导致它错误地放置光标和清除字符。要么使用 PROMPT_COMMAND 来设置 PS1,要么查看 嵌入命令
提示 如果 PROMPT_COMMAND 变得过于复杂,bash-preexec(Bash 对 Zshpreexecprecmd 挂钩函数的实现)可能会使其更易于维护。

命令输入和输出之间的转义

您可以通过不在 PS1 的末尾重置文本属性来影响 Bash 中的输入文本。例如,在 PS1 的末尾插入 tput blink 会使您键入的命令闪烁。但是,这种效果也会继续到命令的输出,因为当您按下 Enter 键时文本属性不会被重置。

为了在输入命令后、输出显示之前插入转义序列,您可以设置 PS0。或者,您可以捕获 Bash 的 DEBUG 信号,该信号在每个命令执行之前发送:

$ trap 'tput sgr0' DEBUG

自定义 root 提示符

为了确保您知道何时以 root 身份运行,您可以自定义您的 root 提示符,使其明显突出(例如,闪烁的红色?)。这是通过像平常一样自定义 Bash 提示符来完成的,但要在 root 的主目录 /root 中。首先将骨架文件 /etc/skel/.bash_profile/etc/skel/.bashrc 复制到 /root,然后根据需要编辑 /root/.bashrc

示例

颜色

本文或本节需要在语言、wiki 语法或风格方面进行改进。请参阅 Help:Style 获取参考。

原因:控制台中的彩色输出 在列出颜色方面有太多重复。应将其缩减至 zsh 的长度。(在 Talk:Bash/Prompt customization 中讨论)
提示 infocmp 显示 tput 可用的颜色数量,例如 colors#8

要查看您的终端支持的全部颜色范围,您可以使用一个简单的 tput 循环(将 setab 改为 setaf 用于文本前景):

for C in {0..255}; do
    tput setab $C
    echo -n "$C "
done
tput sgr0
echo

如果这不起作用(并且您无法通过设置 正确的 TERM 值来修复它),您可以测试不同的手动转义序列:

# standard colors
for C in {40..47}; do
    echo -en "\e[${C}m$C "
done
# high intensity colors
for C in {100..107}; do
    echo -en "\e[${C}m$C "
done
# 256 colors
for C in {16..255}; do
    echo -en "\e[48;5;${C}m$C "
done
echo -e "\e(B\e[m"

要将手动转义从背景更改为前景,标准颜色范围是 30..37,高强度范围是 90..97,48 应该改为 38 以表示 256 色。

常用功能

以下 terminfo 功能对于提示符自定义很有用,并且被许多终端支持。#1#2 是数字参数的占位符。

功能 转义序列 描述
文本属性
blink \e[5m 闪烁文本开
加粗 \e[1m 粗体文本开
dim \e[2m 暗淡文本开
rev \e[7m 反转视频开(切换文本/背景颜色)
sitm \e[3m 斜体文本开
ritm \e[23m 斜体文本关
smso \e[7m 高亮文本开
rmso \e[27m 高亮文本关
smul \e[4m 下划线文本开
rmul \e[24m 下划线文本关
setab #1 \e[4#1m 设置背景颜色 #1 (0-7)
setaf #1 \e[3#1m 设置文本颜色 #1 (0-7)
sgr0 \e(B\e[m 重置文本属性
光标移动
sc \e7 保存光标位置
rc \e8 恢复保存的光标位置
clear \e[H\e[2J 清屏并将光标移至左上角
cuu #1 \e[#1A 光标向上移动 #1
cud #1 \e[#1B 光标向下移动 #1
cuf #1 \e[#1C 光标向右移动 #1
cub #1 \e[#1D 光标向左移动 #1
home \e[H 将光标移至左上角
hpa #1 \e[#1G 将光标移至第 #1
vpa #1 \e[#1d 将光标移至第 #1 行,第一列
cup #1 #2 \e[#1;#2H 将光标移至第 #1 行,第 #2
删除字符
dch #1 \e#1P 删除 #1 个字符(类似于退格)
dl #1 \e#1M 删除 #1
ech #1 \e#1X 清空 #1 个字符(不移动光标)
ed \eE[J 清除屏幕至底部
el \e[K 清除行末尾
el1 \e[1K 清除行开头

可视化退出码

使用与 嵌入命令 相同的技巧,您可以延迟特殊 Bash 变量(如 $?)的插值。因此,以下提示符显示前一个命令的退出代码:

PS1="\$? > "
# or
PS1='$? > '
0 > true
0 > false
1 >

使用条件语句和函数可以使这变得更有趣:

exitstatus()
{
    if [[ $? == 0 ]]; then
        echo ':)'
    else
        echo 'D:'
    fi
}
PS1='$(exitstatus) > '
:) > true
:) > false
D: >

定位光标

可以在 PS1 内部移动屏幕上的光标,使提示符的不同部分出现在不同位置。但是,为了确保 Bash 正确地定位光标和输出,您必须在完成其他位置的打印后将光标移回原始位置。这可以通过 tput 的 scrc 功能来保存和恢复光标位置来实现。移动光标的提示符的通用模式是:

PS1="\[$(tput sc; cursor-moving code) positioned prompt stuff $(tput rc)\] normal prompt stuff"

其中整个重新定位的提示符块被包装在 \[ \] 中,以防止 Bash 将其计算为常规提示符的一部分。

右对齐文本

在屏幕右侧打印文本的最简单方法是使用 printf:

rightprompt()
{
    printf "%*s" $COLUMNS "right prompt"
}

PS1='\[$(tput sc; rightprompt; tput rc)\]left prompt > '
左侧提示符 > 右侧提示符

这会创建一个右对齐的、大小可变的字段 %*s,并将其大小设置为终端的当前列数 $COLUMNS

任意定位

cup 功能可以将光标移动到屏幕上的特定位置,例如 tput cup 20 5 将光标移动到第 20 行、第 5 列(其中 0 0 是左上角)。cuucudcufcub(上、下、前、后)会根据当前位置移动光标。例如 tput cuf 10 会将光标向右移动 10 个字符。您可以使用 LINESCOLUMNS 变量作为参数来相对于底部和右边缘移动光标。例如,要从右下角向左移动 10 行,向左移动 5 列:

$ tput cup $((LINES - 11)) $((COLUMNS - 6))

自定义终端窗口标题

终端窗口标题的自定义方式与提示符非常相似:通过在 shell 中打印转义序列。因此,用户通常会将窗口标题自定义包含在他们的提示符中。虽然这在技术上是 xterm 的一项功能,但许多现代终端都支持它。使用的转义序列是 ESC]2;新标题BEL,其中 ESCBEL 是转义符和响铃字符。使用 Bash 转义序列,在提示符中更改标题如下所示:

PS1='\[\e]2;new title\a\]prompt > '

当然,您的窗口标题字符串可以包含 嵌入命令 的输出或变量,例如 $PWD,这样标题就会随着每个命令而改变。

参见