带有最后一个退出代码的 Bash 提示

Posted

技术标签:

【中文标题】带有最后一个退出代码的 Bash 提示【英文标题】:Bash prompt with the last exit code 【发布时间】:2013-05-18 21:10:33 【问题描述】:

我一直在尝试自定义我的 Bash 提示符,使其看起来像

[feralin@localhost ~]$ _

有颜色。我设法获得了恒定的颜色(每次看到提示时颜色相同),但是如果最后一个命令的退出状态为非零,我希望用户名('feralin')显示为红色,而不是绿色。我想出了:

\e[1;33m[$(if [[ $? == 0  ]]; then echo "\e[0;31m"; else echo "\e[0;32m"; fi)\u\e[m@\e[1;34m\h \e[0;35m\W\e[1;33m]$ \e[m

但是,根据我的观察,$(if ...; fi) 似乎被评估了一次,当.bashrc 运行时,结果将永远被替换。这使得名称始终为绿色,即使最后一个退出代码为非零(如 echo $?)。这是正在发生的事情吗?还是我的提示有其他问题?很长的问题,如何提示我使用最后一个退出代码?

【问题讨论】:

我也从来没有成功过这样的事情;然而,我所做的是将 $?#0 放入提示符中,当且仅当它不为零时,它才会打印数字退出状态。 它按原样工作。你只是把绿色和红色颠倒了。 prompt.gem 的无耻插件,它提供了一个可扩展的提示,包括退出代码和前一个命令的持续时间。 Re:“在运行.bashrc 时评估一次”——这意味着您在分配给PS1 时在其周围加上了错误的引号。必须是单的,而不是双的。如果这没有帮助,那么您将通过在打印提示之前运行的其他东西重置$?set -x 将启用跟踪此类命令。 @CharlesDuffy 感谢您的提示!我会记住的。 【参考方案1】:

当您开始接近复杂的PS1 时,您可能会考虑使用PROMPT_COMMAND。有了这个,你将它设置为一个函数,它将在每个命令之后运行以生成提示。

您可以在 ~/.bashrc 文件中尝试以下操作:

PROMPT_COMMAND=__prompt_command    # Function to generate PS1 after CMDs

__prompt_command() 
    local EXIT="$?"                # This needs to be first
    PS1=""

    local RCol='\[\e[0m\]'

    local Red='\[\e[0;31m\]'
    local Gre='\[\e[0;32m\]'
    local BYel='\[\e[1;33m\]'
    local BBlu='\[\e[1;34m\]'
    local Pur='\[\e[0;35m\]'

    if [ $EXIT != 0 ]; then
        PS1+="$Red\u$RCol"        # Add red if exit code non 0
    else
        PS1+="$Gre\u$RCol"
    fi

    PS1+="$RCol@$BBlu\h $Pur\W$BYel$ $RCol"

这应该可以满足您的要求。如果您想了解我使用 __prompt_command 函数所做的所有事情,请查看我的 bashrc 的 sub file。

【讨论】:

我建议为EXIT 使用至少一个小写字符的变量名,作为向前兼容的做法。见pubs.opengroup.org/onlinepubs/009695399/basedefs/…,第四段——全大写名称的命名空间用于对系统或shell有意义的变量;坚持使用小写名称可防止在仅尝试分配 shell 变量时覆盖环境变量或 shell 内置变量。当然,使用覆盖是临时的本地声明,这使得它比平时少,但仍然不理想。 在 Mac 上,您需要 PROMPT_COMMAND="__prompt_command; $PROMPT_COMMAND" 才能在当前工作目录中打开新选项卡/窗口仍然有效。 PROMPT_COMMAND=update_terminal_cwd 默认情况下会记录 cwd。 Bash 变量PIPESTATUS 提供有关最后执行的命令或管道的更多信息。 $PIPESTATUS[@] 将是一串零,如 0 0 0,以防所有命令都正确执行,否则它将有非零,如 0 127 1。可以通过if $(echo $PIPESTATUS[@] | grep -qEe '^0( 0)*$'); then echo "good"; else echo "bad"; fi 来检查是否成功。 顺便说一句:我和if [ ! -z "$VIRTUAL_ENV" ]; then PS1+=" ($VIRTUAL_ENV##*/)" fi一起工作 @Jani,我推测原因是$? 变量保留了最后执行的命令的值。按 Ctrl+C 或 Enter 不会运行任何命令。【参考方案2】:

如果您不想使用提示命令,则需要考虑两件事:

    首先获取$? 的值。否则会被覆盖。 转义 PS1 中的所有 $(因此在分配时不会对其进行评估)

使用变量的工作示例

PS1="\$(VALU="\$?" ; echo \$VALU ; date ; if [ \$VALU == 0 ]; then echo zero; else echo nonzero; fi) "

没有变量的工作示例

这里 if 必须是第一件事,在任何会覆盖 $? 的命令之前。

PS1="\$(if [ \$? == 0 ]; then echo zero; else echo nonzero; fi) "

注意\$() 是如何被转义的,所以它不会立即执行,而是在每次使用 PS1 时执行。还有\$?的所有用法。

【讨论】:

这真是太棒了!自 1995 年以来,我一直是一个肮脏的老混蛋,我从来没有想过在我的 PS1 中使用命令替换。谢谢。 虽然我更喜欢简单的三元表示法:export PS1="\$([ \$? == 0 ] && echo ✅ || echo ⚠️ ) \h:\W \u\n\$ " 这很酷,我目前正在编辑我的 .bashrc 文件以进行合并。小尼特:“-eq”而不是“==”(和“=”而不是“==”)。 这个解决方案在使用像 python 的 virtualenv 这样的工具时特别有趣,它在激活时为 PS1 变量添加信息。使用PROMPT_COMMAND 不起作用! 如果你使用\$PIPESTATUS[-1],你似乎可以获得最后一个命令的退出状态,即使它不是你的PS1中的第一件事。【参考方案3】:

我想保留默认的Debian 颜色,打印准确的代码,并且只在失败时打印:

# Show exit status on failure.
PROMPT_COMMAND=__prompt_command

__prompt_command() 
    local curr_exit="$?"

    local BRed='\[\e[0;91m\]'
    local RCol='\[\e[0m\]'

    PS1='$debian_chroot:+($debian_chroot)\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

    if [ "$curr_exit" != 0 ]; then
        PS1="[$BRed$curr_exit$RCol]$PS1"
    fi

【讨论】:

【参考方案4】:

紧凑的解决方案:

PS1='... $(code=$?##0;echo $code:+[error: $code])'

这种方法不需要PROMPT_COMMAND(显然有时这可能会更慢)并且如果退出代码非零则打印[error: <code>],如果它为零则不打印:

... > false
... [error: 1]> true
... >

根据您的喜好更改[error: $code] 部分,$code 是要打印的非零代码。

注意使用' 以确保内联$() shell 在稍后评估 PS1 时执行,而不是在启动 shell 时执行。

作为奖励,您可以通过在前面添加\e[01;31m 和在后面添加\e[00m 来重置它:

PS1='... \e[01;31m$(code=$?##0;echo $code:+[error: $code])\e[00m'

--

它是如何工作的:

它使用bash parameter substitution 首先,$?##0 会读取上一条命令的退出代码$? ## 将从开头删除任何 0 模式,从而有效地使 0 结果为空 var(感谢 @blaskovicz 提供的技巧) 我们将其分配给一个临时的 code 变量,因为我们需要进行另一个替换,并且它们不能嵌套 仅当变量 code 设置(非空)时,$code:+REPLACEMENT 才会打印 REPLACEMENT 部分 这样我们可以在它周围添加一些文本和括号,并再次内联引用变量:[error: $code]

【讨论】:

【参考方案5】:

为什么我自己没有考虑到这一点?我发现这很有趣,并将此功能添加到我的“info-bar”项目中。如果最后一个命令失败,眼睛会变红。

#!/bin/bash
eyes=(O o ∘ ◦ ⍤ ⍥) en=$#eyes[@] mouth='_'
face ()  # gen random face
    [[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
    if [[ $1 ]]; then printf "$eyes[$[RANDOM%en]]$mouth$eyes[$[RANDOM%en]]"
                 else printf "$ecolor$eyes[$[RANDOM%en]]$YLW$mouth$ecolor$eyes[$[RANDOM%en]]$DEF"
    fi

info ()  error=$?
    [[ -d .git ]] &&   # If in git project folder add git status to info bar output
        git_clr=('GIT' $(git -c color.ui=always status -sb)) # Colored output 4 info
        git_tst=('GIT' $(git                    status -sb)) # Simple  output 4 test
    
    printf -v line "%$COLUMNSs"                            # Set border length
    date=$(printf "%(%a %d %b %T)T")                         # Date & time 4 test
    test=" O_o $PWD  $git_tst[*] $date o_O "               # Test string
    step=$[$COLUMNS-$#test]; [[ $step -lt 0 ]] && step=0   # Count spaces
    line="$GRN$line// /-$DEF\n"                            # Create lines
    home="$BLD$BLU$PWD$DEF"                                  # Home dir info
    date="$DIM$date$DEF"                                     # Colored date & time
           #------+-----+-------+--------+-------------+-----+-------+--------+
           # Line | O_o |homedir| Spaces | Git  status | Date|  o_O  |  Line  |
           #------+-----+-------+--------+-------------+-----+-------+--------+
    printf "$line $(face) $home %$steps $git_clr[*] $date $(face) \n$line" # Final info string

PS1='$debian_chroot:+($debian_chroot)\n$(info)\n$ '
case "$TERM" in xterm*|rxvt*)
    PS1="\[\e]0;$debian_chroot:+($debian_chroot) $(face 1) \w\a\]$PS1";;
esac

【讨论】:

【参考方案6】:

改进了demure answer:

我认为这很重要,因为退出状态并不总是 0 或 1。

if [ $EXIT != 0 ]; then
    PS1+="$Red$EXIT:\u$RCol"      # Add red if exit code != 0
else
    PS1+="$Gre$EXIT:\u$RCol"      # Also displays exit status
fi

【讨论】:

你有没有引用扩展的原因?如果具有此 PROMPT_COMMAND 的用户设置了 IFS=0,那么扩展时这将变为 if [ != 0 ](当然退出代码为零);让它if [ "$EXIT" != 0 ] 将避免这种情况。【参考方案7】:

要保留原始提示格式(不仅仅是颜色), 您可以将以下内容附加到文件 ~/.bashrc 的末尾:

PS1_ORIG=$PS1 # original primary prompt value
PROMPT_COMMAND=__update_prompt # Function to be re-evaluated after each command is executed
__update_prompt() 
    local PREVIOUS_EXIT_CODE="$?"
    if [ $PREVIOUS_EXIT_CODE != 0 ]; then
        local RedCol='\[\e[0;31m\]'
        local ResetCol='\[\e[0m\]'
        local replacement="$RedCol\u$ResetCol"
    
        # Replace username color
        PS1=$PS1_ORIG//]\\u/]$replacement
        ## Alternative: keep same colors, append exit code
        #PS1="$PS1_ORIG[$RedColerror=$PREVIOUS_EXIT_CODE$ResetCol]$ "
    else
        PS1=$PS1_ORIG
    fi

另请参阅有关保留用户名颜色并将红色错误代码附加到原始提示格式末尾的替代方法的评论。

【讨论】:

这个解决方案有点基于端庄和 Velkan 的想法/解决方案(但保留了原始格式和颜色)【参考方案8】:

当退出代码为零时,以下提供一个前导的绿色复选标记,在所有其他情况下提供一个红叉。其余的是标准的彩色提示。可以修改 printf 语句以呈现最初请求的两种状态。

PS1='$(if [ $? -eq 0 ]; then printf "\033[01;32m""\xE2\x9C\x93"; else printf "\033[01;31m""\xE2\x9C\x95"; fi) \[\e[00;32m\]\u@\h\[\e[00;30m\]:\[\e[01;33m\]\w\[\e[01;37m\]\$ '

【讨论】:

以上是关于带有最后一个退出代码的 Bash 提示的主要内容,如果未能解决你的问题,请参考以下文章

我可以将退出代码 %errorlevel% 从 WSL Bash 返回到 Powershell 或命令提示符吗?

在 bash 中添加(收集)退出代码

从最后一个管道(stdin)获取退出代码

bash自定义退出状态码exit

返回与 bash 脚本中返回的命令不同的退出代码

Linux Bash-脚本基础