Bash prompt with the last exit code

2024-10-29 08:35:00
admin
原创
192
摘要:问题描述: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.

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用