PS1和PROMPT_COMMAND之间有什么区别?

151

在查看这个神奇的帖子时,我注意到一些示例使用了

PS1="Blah Blah Blah"

还有一些使用

PROMPT_COMMAND="Blah Blah Blah"

在设置Bash shell提示符时,有些人使用单引号,有些人使用双引号。这两者之间有什么区别?我在Stack Overflow上搜索了一下,甚至进行了更广泛的Google搜索,都没有找到答案,所以即使给出一个正确的查找答案的链接也会很感激。

7个回答

82

PROMPT_COMMAND可以包含普通的Bash语句,而PS1变量也可以包含特殊字符,比如'\h'代表主机名,加入到变量中。

例如,这是我使用了PROMPT_COMMAND和PS1的Bash提示符。在PROMPT_COMMAND中的Bash代码确定你可能处于的Git分支,并在提示符中显示它,还有最后运行的进程的退出状态、主机名和pwd的基本名称。

变量RET存储上一个执行的程序的返回值。这很方便,可以查看是否出现错误以及终端中执行的最后一个程序的错误代码。请注意整个PROMPT_COMMAND表达式外面的单引号。它包括PS1,以便每次评估PROMPT_COMMAND变量时重新评估该变量。

PROMPT_COMMAND='RET=$?;\
  BRANCH="";\
  ERRMSG="";\
  if [[ $RET != 0 ]]; then\
    ERRMSG=" $RET";\
  fi;\
  if git branch &>/dev/null; then\
    BRANCH=$(git branch 2>/dev/null | grep \* |  cut -d " " -f 2);\
  fi;
PS1="$GREEN\u@\h $BLUE\W $CYAN$BRANCH$RED$ERRMSG \$ $LIGHT_GRAY";'

在非Git目录中,示例输出看起来像这样:

sashan@dhcp-au-122 Documents  $ false
sashan@dhcp-au-122 Documents  1 $

在Git目录中,你可以看到分支名称:

sashan@dhcp-au-122 rework mybranch $

更新

在阅读了评论和Bob的答案之后,我认为按照他所描述的编写代码更好。这比我上面最初编写的方式更易于维护,因为PS1变量是在PROMPT_COMMAND内部设置的,而PROMPT_COMMAND本身是一个超级复杂的字符串,在Bash中运行时进行评估。

它能够工作,但比必要的要复杂。公平地说,我大约10年前为自己编写了那个PROMPT_COMMAND,它能够正常运行,我没有过多考虑。

对于那些好奇我如何修改自己的东西的人,我基本上将PROMPT_COMMAND的代码放在一个单独的文件中(如Bob所述),然后输出我打算成为PS1的字符串:

GREEN="\[\033[0;32m\]"
CYAN="\[\033[0;36m\]"
RED="\[\033[0;31m\]"
PURPLE="\[\033[0;35m\]"
BROWN="\[\033[0;33m\]"
LIGHT_GRAY="\[\033[0;37m\]"
LIGHT_BLUE="\[\033[1;34m\]"
LIGHT_GREEN="\[\033[1;32m\]"
LIGHT_CYAN="\[\033[1;36m\]"
LIGHT_RED="\[\033[1;31m\]"
LIGHT_PURPLE="\[\033[1;35m\]"
YELLOW="\[\033[1;33m\]"
WHITE="\[\033[1;37m\]"
RESTORE="\[\033[0m\]" #0m restores to the terminal's default colour

if [ -z $SCHROOT_CHROOT_NAME ]; then
    SCHROOT_CHROOT_NAME=" "
fi
BRANCH=""
ERRMSG=""
RET=$1
if [[ $RET != 0 ]]; then
    ERRMSG=" $RET"
fi
if which git &>/dev/null; then
    BRANCH=$(git branch 2>/dev/null | grep \* |  cut -d " " -f 2)
else
    BRANCH="(git not installed)"
fi
echo "${GREEN}\u@\h${SCHROOT_CHROOT_NAME}${BLUE}\w \
${CYAN}${BRANCH}${RED}${ERRMSG} \$ $RESTORE"

在我的.bashrc文件中:

function prompt_command {
    RET=$?
    export PS1=$(~/.bash_prompt_command $RET)
}
PROMPT_DIRTRIM=3
export PROMPT_COMMAND=prompt_command

2
您可以将其中一行缩短为:if git branch &>/dev/null ; then\。它将stdout和stderr都重定向到/dev/null。http://www.tldp.org/LDP/abs/html/io-redirection.html - user184968
5
无需导出 PROMPT_COMMAND - dolmen
2
我不明白为什么在 PROMPT_COMMAND 中在线改变 PS1 不可取。这是完美有用的代码。与 Bob 的回答不同,PS1 变量已经正确构建。这使得基于您当前情况的更复杂的 bash 提示符成为可能。 - Christian Wolf
5
PROMPT_COMMAND中构建PS1没有任何意义,这只是一个不好的示例。应该在.bash_profile中构建一次PS1,并使用单引号而不是双引号,这样变量替换将在每个提示符中被计算。 - pal
3
避免在PROMPT_COMMAND中设置PS1的一个原因是,如果这样做,您永远无法在运行时重置它,因为它将不断被重置。虽然并不是非常常见,但当发生这种情况时仍然会让人感到困惑。 - hraban
显示剩余6条评论

75

来自GNU Bash文档页面(Bash参考手册):

PROMPT_COMMAND
    If set, the value is interpreted as a command to execute before
    the printing of each primary prompt ($PS1).

我从未使用过它,但当我只有sh时我可能会用到它。


56
区别在于PS1是实际使用的提示字符串,而PROMPT_COMMAND是在提示之前执行的命令。如果你想要最简单、最灵活的方式来构建提示符,请尝试以下操作:
将此内容放入您的.bashrc文件中:
function prompt_command {
  export PS1=$(~/bin/bash_prompt)
}
export PROMPT_COMMAND=prompt_command

然后编写一个脚本(Bash,PerlRuby:由您选择),并将其放置在文件 ~/bin/bash_prompt 中。

该脚本可以使用任何信息来构建提示符。在我看来,这要简单得多,因为您不必学习为PS1变量开发的有点古怪的替换语言。

您可能认为只需将PROMPT_COMMAND直接设置为~/bin/bash_prompt,并将PS1设置为空字符串即可实现相同的功能。

这一开始似乎有效,但很快您就会发现readline代码希望将PS1设置为实际提示符,当您向后滚动历史记录时,结果会混乱。

此解决方法使PS1始终反映最新的提示符(因为该函数设置了由调用实例的shell使用的实际PS1变量),这使得readline和命令历史记录正常工作。


23
不要在PROMPT_COMMAND中设置PS1!应该在PROMPT_COMMAND中设置变量并在PS1中使用它们。否则,您将失去使用PS1转义序列(如\u\h)的能力。您必须在PROMPT_COMMAND中重新创建它们。这可能是可行的,但是无法解决丢失\[\]的问题,它们标记不可打印字符的开始和结束。这意味着您无法在不让终端混淆提示长度的情况下使用颜色。这会导致在编辑生成两行的命令时混淆readline。最终你会在屏幕上看到一团乱麻。 - ceving
2
@ceving 确实如此!可以使用 PROMPT_COMMAND 来更改您的 PS1 的格式,从而获得双重好处。 - kbtz
4
PROMPT_COMMAND 会在打印 PS1 之前执行。我认为从 PROMPT_COMMAND 中设置 PS1 没有问题,因为在 PROMPT_COMMAND 完成后,shell 将打印经过修改的 PS1,该修改是在 PROMPT_COMMAND 中完成的(或者在这种情况下,在 prompt_command 中完成)。 - Felipe Alvarez
7
警告:通常不应直接使用PROMPT_COMMAND将字符打印到提示符。在PS1之外打印的字符不会被Bash计算,这将导致它错误地放置光标并清除字符。要么使用PROMPT_COMMAND设置PS1,要么查看嵌入命令。 - Northstrider
5
我不明白为什么每个人都试图在PROMPT_COMMAND中使用一些技巧,而不是在PS1中使用命令替换。export PS1='$(~/bin/bash_prompt)'可以做同样的事情,但看起来更加清晰明了。 - pal
显示剩余3条评论

16

来自bash手册:

PROMPT_COMMAND

如果设置了该变量,其值将在发出每个主提示符之前作为命令执行。

PS1

该参数的值会被扩展(参见下面的“提示”),并用作主提示符字符串。默认值为''\s-\v\$ ''。

如果仅想设置提示符字符串,则仅需使用PS1即可:

PS1='user \u on host \h$ '

如果您想在打印提示信息之前执行其他操作,请使用PROMPT_COMMAND。例如,如果您想将缓存的写入同步到磁盘上,可以编写以下内容:
PROMPT_COMMAND='sync'

1
你也可以通过 PS1 设置终端的标题,而不需要使用 PROMPT_COMMAND。只需在 PS1 中包含设置标题的序列,并用 \[\] 包装即可。 - dolmen
1
@dolmen 好的。那么让我们做点别的,比如动态设置环境变量。 - Cyker
@Cyker,你可以在PS1中动态设置环境变量,但它只会在子shell中设置,所以你无法获取其值。但是你的示例很简单:PS1='$(sync)user \u on host \h$ ' - pal

3

是的,为了真正掌握这个:

  • PROMPT_COMMAND 是一个方便的 Bash 变量/函数,但严格来说,也可以仅使用 PS1 来完成所有操作,对吗?

我的意思是,如果想要 设置 具有提示范围之外作用域的 另一个 变量:取决于 shell,该变量可能需要在 $PS1 之外先声明,或者(最坏情况下)可能需要在调用 $PS1 之前使用某些等待 FIFO 的技巧(并在 $PS1 结束时再次启动);\u \h 可能会引起一些麻烦,特别是如果您正在使用一些复杂的正则表达式;但除此之外:通过在 $PS1 中使用命令替换(和在某些角落情况下使用显式子 shell),可以完成任何 PROMPT_COMMAND 可以做到的事情?

对吗?


2

区别在于:

  • 如果你从PROMPT_COMMAND输出不完整的一行,会破坏你的Bash提示符。
  • PS1替换\H等内容。
  • PROMPT_COMMAND运行其内容,PS1使用其内容作为提示符。

PS1每次提示符时进行变量展开和命令替换。没有必要使用PROMPT_COMMAND分配一个值给PS1或者运行任意代码。你可以在文件.bash_profile中只执行一次export PS1='$(uuidgen) $RANDOM'。只需要使用单引号。


1

我花了很多时间研究这个问题,现在我想分享一下我的解决方案。我查看了许多关于PROMPT_COMMAND和PS1的SO帖子,并尝试了许多单引号、双引号、调用函数的组合……但是我无法在不打印控制字符或展开但未处理提示字符串的情况下每次更新提示,也不能像我们被建议的那样仅在PROMPT_COMMAND中设置PS1。我的问题在于设置包含控制字符的变量(颜色);这些必须在PS1中的变量名称后硬编码。PROMPT_COMMAND设置为一个设置变量并在双引号PS1字符串中使用(转义)它们的函数。这是一个类似于powerline风格的提示符,每次命令都会更改颜色。

icon1=#unicode powerline char like
#these: https://github.com/ryanoasis/powerline-extra-symbols#glyphs
icon2=#same

#array of ANSI colors. 2 for rgb mode then the rgb values
#then 'm' without '\]' control character. these are from
#the solarized theme https://ethanschoonover.com/solarized/
declare -a colors=(
  "2;220;50;47m"
  "2;203;75;22m"
  "2;181;137;0m"
  "2;133;153;0m"
  "2;42;161;152m"
  "2;38;139;210m"
  "2;108;113;196m"
  "2;211;54;130m"
  "2;0;43;54m"
  "2;7;54;66m"
  "2;88;110;117m"
  "2;101;123;131m"
  "2;131;148;150m"
  "2;147;161;161m"
)
#outside of vars set in PROMPT_COMMAND it's ok to have control chars
LEN=${#colors[@]}
BG="\[\e[48;"#set bg color
FG="\[\e[38;"#set fg color
TRANSP="1m\]"#transparency
BASE2="2;238;232;213m\]"#fg (text) color

myfunc(){
  RAND=$(($RANDOM % $LEN))
  COLOR1=${colors[$RAND]}
  COLOR2=${colors[($RAND + 1) % $LEN]}
  COLOR3=${colors[($RAND + 2) % $LEN]}
}

PROMPT_COMMAND=myfunc

#note double quotes and escaped COLOR vars followed by hard-coded '\]' control chars
PS1="$BG$TRANSP$FG\$COLOR1\]$icon1$BG\$COLOR1\]$FG$TRANSP$BG\$COLOR1\]$FG$BASE2 
[username hard-coded in unicode] $BG\$COLOR2\]$FG\$COLOR1\]$icon2$BG\$COLOR2\]$FG$BASE2 
\w $BG\$COLOR3\]$FG\$COLOR2\]$icon2$BG\$COLOR3\]$FG$BASE2 [more unicode] 
\[\e[0m\]$FG\$COLOR3\]$icon2\[\e[0m\] "

那应该能让你开始了!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接