Bash prompt with the last exit code

2024-10-29 08:35:00
admin
原创
53
摘要:问题描述:I've been trying to customize my Bash prompt so that it will look like[feralin@localhost ~]$ _ with colors. I managed to get constant colors (the...

问题描述:

I've been trying to customize my Bash prompt so that it will look like

[feralin@localhost ~]$ _

with colors. I managed to get constant colors (the same colors every time I see the prompt), but I want the username ('feralin') to appear red, instead of green, if the last command had a nonzero exit status. I came up with:

e[1;33m[$(if [[ $? == 0  ]]; then echo "e[0;31m"; else echo "e[0;32m"; fi)/ue[m@e[1;34mh e[0;35mWe[1;33m]$ e[m

However, from my observations, the $(if ...; fi) seems to be evaluated once, when the .bashrc is run, and the result is substituted forever after. This makes the name always green, even if the last exit code is nonzero (as in, echo $?). Is this what is happening? Or is it simply something else wrong with my prompt? Long question short, how do I get my prompt to use the last exit code?


解决方案 1:

As you are starting to border on a complex PS1, you might consider using PROMPT_COMMAND. With this, you set it to a function, and it will be run after each command to generate the prompt.

You could try the following in your ~/.bashrc file:

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}"
}

This should do what it sounds like you want. Take a look a my bashrc's sub file if you want to see all the things I do with my __prompt_command function.

解决方案 2:

If you don't want to use the prompt command there are two things you need to take into account:

  1. getting the value of $? before anything else. Otherwise it'll be overridden.

  2. escaping all the $'s in the PS1 (so it's not evaluated when you assign it)

Working example using a variable

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

Working example without a variable

Here the if needs to be the first thing, before any command that would override the $?.

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

Notice how the $() is escaped so it's not executed right away, but each time PS1 is used. Also all the uses of $?.

解决方案 3:

Compact solution:

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

This approach does not require PROMPT_COMMAND (apparently this can be slower sometimes) and prints [error: <code>] if the exit code is non-zero, and nothing if it's zero:

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

Change the [error: ${code}] part depending on your liking, with ${code} being the non-zero code to print.

Note the use of ' to ensure the inline $() shell gets executed when PS1 is evaluated later, not when the shell is started.

As bonus, you can make it colorful in red by adding e[01;31m in front and e[00m after to reset:

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

--

How it works:

  • it uses bash parameter substitution

  • first, the ${?##0} will read the exit code $? of the previous command

  • the ## will remove any 0 pattern from the beginning, effectively making a 0 result an empty var (thanks @blaskovicz for the trick)

  • we assign this to a temporary code variable as we need to do another substitution, and they can't be nested

  • the ${code:+REPLACEMENT} will print the REPLACEMENT part only if the variable code is set (non-empty)

  • this way we can add some text and brackets around it, and reference the variable again inline: [error: ${code}]

解决方案 4:

I wanted to keep default Debian colors, print the exact code, and only print it on failure:

# 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)}[]/u@h[]:[]w[]$ '

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

解决方案 5:

The following provides a leading green check mark when the exit code is zero and a red cross in all other cases. The remainder is a standard colorized prompt. The printf statements can be modified to present the two states that were originally requested.

PS1='$(if [ $? -eq 0 ]; then printf """xE2x9Cx93"; else printf """xE2x9Cx95"; fi) [e[00;32m]/u@h[e[00;30m]:[e[01;33m]w[e[01;37m]$ '

解决方案 6:

Why didn't I think about that myself? I found this very interesting and added this feature to my 'info-bar' project. Eyes will turn red if the last command failed.

#!/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 "%${COLUMNS}s"                            # 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
"                            # 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 %${step}s ${git_clr[*]} $date $(face) 
$line" # Final info string
}
PS1='${debian_chroot:+($debian_chroot)}
$(info)
$ '
case "$TERM" in xterm*|rxvt*)
    PS1="[e]0;${debian_chroot:+($debian_chroot)} $(face 1) wa]$PS1";;
esac

Enter image description here

解决方案 7:

In bash, it's possible to show return codes only on failure, i.e. non-zero exit of the previous command, and without polluting exit status variables state, as PROMPT_COMMAND & $(...) subshells do, with

PS1="${?#0} $ "

prompt lines color variations with zero & non-zero exit codes
prompt lines color variations with zero & non-zero exit codes

In PS1 context,

  • ${?#0} defers evaluation of ?, last exit status, and removes leading 0, which only occurs for 0 in 0-255 possible exit statuses.

  • $ appears as # when running as root, and $ as a normal user

$ man bash
BASH(1) ...
PARAMETERS ...
  Special Parameters ...
     ?      Expands to the exit status of the most recently executed foreground pipeline.

It's helpful to avoid PROMPT_COMMAND and $(...) subshells, because they pollute the last exit code with their own exit code. Kludges to work around this can include returning previous exit codes explicitly, but it's easy to miss, tricky to get right, not robust.

styling

Wrap this inclusion in ANSI color codes, and it can stand out too.

declare -A _c
_c[red_ul]=""
_c[nc]=""
PS1="${_c[red_ul]}${?##0}${_c[nc]} $ "

Note: Escape the $ prompt indicator, so it shows as # in a root shell ("EUID = 0").

testing it out

_ret(){ return ${1:-0};}
$ _ret
$ _ret 1
1 $ 
1 $ _ret 2
2 $ :
$

bash's PIPESTATUS

Use echo "${PIPESTATUS[@]}" or declare -p PIPESTATUS to preview this value.

You can include it in PS1 as "${PIPESTATUS[@]}", but recommend you color it in a muted way. No fancy tricks for hiding the last return, 1-command exit 0.

You can test with $ _ret 5 | _ret 6 followed by $ echo "${PIPESTATUS[@]}" before including it in a temporary, then permanent PS1.

解决方案 8:

Improved demure answer:

I think this is important because the exit status is not always 0 or 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

解决方案 9:

Bash

function my_prompt {
    local retval=$?
    local field1='/u@h'
    local field2='w'
    local field3='$([ $SHLVL -gt 1 ] && echo  shlvl:$SHLVL)$([ j -gt 0 ] && echo  jobs:j)'"$([ ${retval} -ne 0 ] && echo  exit:$retval)"
    local field4='$'

    PS1=$'
'"e[0;35m${field1}e[m e[0;34m${field2}e[me[0;31m${field3}e[m"$'
'"[e[0;36m]${field4}[e[m] "


}

PROMPT_COMMAND="my_prompt; ${PROMPT_COMMAND}"

Zsh

PROMPT=$'
''%F{magenta}%n@%m%f %F{blue}%~%f%F{red}%(2L. shlvl:%L.)%(1j. jobs:%j.)%(?.. exit:%?)%f'$'
''%F{cyan}%(!.#.$)%f '

Images of prompt

image of prompt
image of prompt

解决方案 10:

To preserve the original prompt format (not just colors),
you could append following to the end of file ~/.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[${RedCol}error=$PREVIOUS_EXIT_CODE${ResetCol}]$ "
    else
        PS1=$PS1_ORIG
    fi
}

See also the comment about the alternative approach that preserves username color and just appends an error code in red to the end of the original prompt format.

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用