Search code examples
bashreturn-valuecommand-promptzshprompt

Bash and Zsh prompt that ring a bell and do display error code of last command


I want to have the same prompt in both Bash and Zsh. And I want it to:

  • bell a ring, if the last command failed, and
  • display its error code.

In Bash, I do have:

BLK="\[$(tput setaf 0; tput bold)\]"
RED="\[$(tput setaf 1; tput bold)\]"
grn="\[$(tput setaf 2)\]"
GRN="\[$(tput setaf 2; tput bold)\]"
yel="\[$(tput setaf 3)\]"
reset_color="\[$(tput sgr0)\]"

PS1='\n\
`if [[ $? -gt 0 ]]; then printf "\[\033[01;31m\]$?"; tput bel; else printf "\[\033[01;32m\]0"; fi`\
\[\033]0;$PWD\007\] \
\[\033[0;32m\]\u@\h\
\[\033[01;30m\]:\
\[\033[;;33m\]\w\
\[\033[36m\]`__git_ps1`\
\[\033[0m\]\n$ '

In Zsh, that's my config:

BLK=$(tput setaf 0; tput bold)
RED=$(tput setaf 1; tput bold)
grn=$(tput setaf 2)
GRN=$(tput setaf 2; tput bold)
yel=$(tput setaf 3)
reset_color=$(tput sgr0)

PROMPT="
%(?.$GRN.$RED)%?$reset_color $grn%n@%m$BLK:$reset_color$yel%~ $reset_color
%(!.#.$) "

And this is how it looks like in the terminal:

enter image description here

Both prompts do ring the bell when there is an error with the last command. But, in Bash, it prints 0 instead of the right return code of the command that failed.

How to fix that?

PS- Any better way to improve the above code is welcomed!


Solution

  • The command to test $? itself resets $? to the result of the test. You need to save the value you want to display first.

    PS1='\n\
    $(st=$?; if [[ $st -gt 0 ]]; then printf "\[\033[01;31m\]$st"; tput bel; else printf "\[\033[01;32m\]0"; fi)\
    \[\033]0;$PWD\007\] \
    \[\033[0;32m\]\u@\h\
    \[\033[01;30m\]:\
    \[\033[;;33m\]\w\
    \[\033[36m\]`__git_ps1`\
    \[\033[0m\]\n$ '
    

    I would recommend building up the value of PS1 using PROMPT_COMMAND, instead of embedding executable code. This gives you more flexibility for commenting and separating any computations you need from the actual formatting. make_prompt doesn't need quite so many lines, but it's just a demonstration.

    set_title () {
        printf '\033]0;%s%s' "$1" "$(tput bel)"
    }
    
    make_prompt () {
      local st=$?
      local c bell
      bell=$(tput bel)
    
      # Green for success, red and a bell for failure
      if [[ $st -gt 0 ]]; then
        c=31
      else
        c=32 bell=
      fi
     
      win_title=$(set_title "$PWD")
      git_status=$(__git_ps1)
    
      PS1="\n"
      PS1+="\[\e[01;${c}m$bell\]"  # exit status color and bell
      PS1+=$st
      PS1+="\[$win_title\]"  # Set the title of the window
      PS1+="\[\e[0;32m\]"    # Color for user and host
      PS1+="\u@\h"
      PS1+="\[\e[01;30m\]"   # Color for : separator
      PS1+=":"
      PS1+="\[\e[;;33m\]"    # Color for directory
      PS1+="\w"
      PS1+="\[\e[36m\]"      # Color for git branch
      PS1+=$git_status
      PS1+="\[\e[0m\]"       # Reset to terminal defaults
      PS1+="\n$ "
    }
    
    PROMPT_COMMAND=make_prompt
    

    zsh already has terminal-agnostic escape sequences for adding color.

    PROMPT="%B%(?.%F{green}.%F{red}$(tput bel))%?%f%b %F{green}%n@%m%F{black}%B:%b%F{yellow}%~ %f%(!.#.$) "