跳转至内容

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 username $

请参阅 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 转义序列

遗憾的是,某些有效的 ANSI 转义序列可能在你的终端 terminfo 数据库中缺失。这在支持 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,且 256 色应将 48 改为 38。

常用功能

以下 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 为左上角)。cuu, cud, cufcub(上、下、前、后)相对于当前位置移动光标。例如 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 等变量,以便标题随每个命令而变化。

参见

© . This site is unofficial and not affiliated with Arch Linux.

Content is available under GNU Free Documentation License 1.3 or later unless otherwise noted.