2

I am running Ubuntu 18.04, and I would like to set my bash prompt so it reads like below:

user:~/Documents [14:22:07] 1 $

My PS1 is as follows:

ALERT_COLOR="$(tput setaf 1)"

# Display unsuccessful exit codes
function exit_status {
    last_status=$?
    if [[ $last_status != 0 ]]; then
        echo "$ALERT_COLOR[$last_status]"
    fi  
}

parse_git_branch() {
 git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}
if [ "$color_prompt" = yes ]; then
 PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u:\[\033[01;34m\]\w\[\033[01;31m\]$(parse_git_branch)\[\033[00m\] \[\033[36m\][\t] \[$ALERT_COLOR\]$exit_status \[\033[0;37m\]\$ '
else
 PS1='${debian_chroot:+($debian_chroot)}\u:\w$(parse_git_branch)\$ '
fi
unset color_prompt force_color_prompt

Everything works apart from the exit code. It never displays the exit code, and if I just do $? in my PS1, then it always shows as 0

Luke
  • 121

2 Answers2

7

The issue is here:

PS1='...\[$ALERT_COLOR\]$exit_status ...\$ '
                        ^^

That is a parameter expansion, it doesn't call the function you've set up. You need to call the function within a command substitution, e.g. $(exit_status), or from PROMPT_COMMAND. If you do, take care with the \[ .. \] escapes: Bash interprets them before other expansions in the prompt, so you have to hardcode them in the prompt string (they can't be parts of variables or other things expanded in the prompt).

And if not expanding the prompt escapes from variables seems backwards to you, I can't blame you. But that's the way it's documented:

In addition, the following table describes the special characters which can appear in the prompt variables PS1 to PS4: [...] After the string is decoded, it is expanded via parameter expansion, command substitution, [...]

Something like this should work:

normal_color=$'\033[00m'
red_color=$'\033[41m'
exit_color=$normal_color

set_exit_color() {
    if [ "$?" != 0 ]; then
        exit_color=$red_color
    else
        exit_color=$normal_color
    fi
}

PROMPT_COMMAND=set_exit_color
PS1='\[$exit_color\][$?]\[$normal_color\] \w\$ '

I would have thought it'd need a temporary variable to hold the exit status, but apparently PROMPT_COMMAND doesn't modify the value of $? that's expanded in the prompt. If call the function with a command substitution from inside the prompt string, then you need a workaround, as the exit code of the command substitution takes effect. Something like this:

normal_color=$'\033[00m'
red_color=$'\033[41m'
exit_color=$normal_color

exit_color() {
    exit_code=$?
    if [ "$exit_code" != 0 ]; then
        echo "$red_color"
    else
        echo "$normal_color"
    fi
    return "$exit_code"
}

PS1='\[$(exit_color)\][$?]\[$normal_color\] \w\$ '

I would use the version with PROMPT_COMMAND, just to save the subshell fork caused by the command substitution, but in practice the effect is minimal.

ilkkachu
  • 138,973
1

I like to have a 3 line prompt, so I do this:

۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰ bash ۰۰ 10:56:52
jackman@jackmanVM:~/tmp
+$ cat ~/.bash_prompt
# ... some other stuff ...

__bash_prompt() {
    local last_status=$1
    local cwd=$( sed "s,^$HOME,~," <<<"$PWD" )
    local user_host_path="${debian_chroot:+($debian_chroot) }$(id -un)@$(hostname -s):$cwd"

    # terminal title
    echo -ne "\e]0;$user_host_path\a"

    # separator and date
    local char="۰"
    printf "%s bash %s " "$(jot -s "" -b "$char" $(( $(tput cols) - 18 )) )" "$char$char"
    date '+%T'

    if ((last_status != 0)); then
        local color_bold='\e[0;1m'
        local color_reset='\e[0m'
        printf "$color_bold[%d]$color_reset " $last_status
    fi

    # user@host, directory, git branch
    printf "%s%s\n" "${user_host_path}$(git_current_branch " (%s)")"
}

# ref: https://stackoverflow.com/questions/1039713/different-bash-prompt-for-different-vi-editing-mode
# a single-line PS1 allows the show-mode-in-prompt inputrc setting to be useful
PROMPT_COMMAND='__bash_prompt $?'
PS1='\$ '

I source that file in my ~/.bashrc

The exit status shows up like this

۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰ bash ۰۰ 11:05:10
jackman@jackmanVM:~/tmp
+$ sh -c 'exit 42'
۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰ bash ۰۰ 11:05:14
[42] jackman@jackmanVM:~/tmp
+$ false
۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰ bash ۰۰ 11:05:16
[1] jackman@jackmanVM:~/tmp
+$ true
۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰ bash ۰۰ 11:05:17
jackman@jackmanVM:~/tmp
+$ 

Note that the first thing I do in the function is to stash the last exit status, before I execute any other commands

I use the PROMPT_COMMAND to output all the fancy stuff and have just a simple PS1 because I use vi mode and I like to see the vi mode indicator. I also have this:

۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰ bash ۰۰ 10:57:21
jackman@jackmanVM:~/tmp
+$ cat ~/.inputrc
set editing-mode vi

set show-mode-in-prompt on
$if Bash
    # not until bash 4.4, I believe
    # escape sequences: https://stackoverflow.com/a/42107711/7552 
    #set vi-ins-mode-string "+\1\e[5 q\2"
    #set vi-cmd-mode-string ":\1\e[1 q\2"
    set vi-ins-mode-string +
    set vi-cmd-mode-string :
$endif

I use as my login shell, and occassionally drop into ksh, so I put the name of the shell in the prompt separator so I can keep track of what I'm typing.

glenn jackman
  • 85,964