Posts in category bash

More fun with the bash prompt

A few years ago, I posted some fun Bash prompt tools, part of which was adding emoticons to the prompt based on the previous command's exit code. I figured it was time to revisit that bit of fun with a couple of enhancements.

First, a bit of code cleanup so color choices are more obvious, and add faces for SIGILL and SIGKILL.

#!/bin/bash
# source this
function prompt_smile () {
    local retval=$?
    local red=196
    local yellow=226
    local green=46
    local darkgreen=28
    if [ $retval -eq 0 ]; then
        color=$green
        face=":)"
    elif [ $retval -eq 1 ]; then
        color=$red
        face=":("
    elif [ $retval -eq 130 ]; then # INT
        color=$yellow
        face=":|"
    elif [ $retval -eq 132 ]; then # ILL
        color=$darkgreen
        face=":-&"
    elif [ $retval -eq 137 ]; then # KILL
        color=$red
        face="X_X"
    elif [ $retval -eq 139 ]; then # SEGV
        color=$red
        face=">_<"
    elif [ $retval -eq 143 ]; then # TERM
        color=$red
        face="x_x"
    else
        color=$red
        face="O_o"
    fi
    echo -e "\001$(tput setaf $color; tput bold)\002$face\001$(tput sgr0)\002"
    return $retval # preserve the value of $?
}
PS1="$PS1\$(prompt_smile) "

Download

When sourced into your shell with . promptsmile.sh, you get results like this:

bash-4.4$ . promptsmile.sh
bash-4.4$ :) false
bash-4.4$ :( true
bash-4.4$ :) sleep 60 & X=$!; (sleep 1; kill -INT $X) & fg %1
[1] 26699
[2] 26700
sleep 60

[2]+  Done                    ( sleep 1; kill -INT $X )
bash-4.4$ :| sleep 60 & X=$!; (sleep 1; kill -ILL $X) & fg %1
[1] 26709
[2] 26710
sleep 60
Illegal instruction (core dumped)
[2]   Done                    ( sleep 1; kill -ILL $X )
bash-4.4$ :-& sleep 60 & X=$!; (sleep 1; kill -KILL $X) & fg %1
[1] 26776
[2] 26777
sleep 60
Killed
[2]+  Done                    ( sleep 1; kill -KILL $X )
bash-4.4$ X_X sleep 60 & X=$!; (sleep 1; kill -SEGV $X) & fg %1
[1] 26788
[2] 26789
sleep 60
Segmentation fault (core dumped)
[2]   Done                    ( sleep 1; kill -SEGV $X )
bash-4.4$ >_< sleep 60 & X=$!; (sleep 1; kill -TERM $X) & fg %1
[1] 26852
[2] 26853
sleep 60
Terminated
[2]+  Done                    ( sleep 1; kill -TERM $X )
bash-4.4$ x_x (exit 4)
bash-4.4$ O_o true
bash-4.4$ :) exit

One bit of feedback I received was that the use of :) vs x_x meant that command prompts would shift by a character, and it would be better to have all the emoticons be of the same width. So if you prefer your faces all the same width, promptsmile-3wide.sh gives you more consistent line lengths:

#!/bin/bash
# source this
function prompt_smile () {
    local retval=$?
    local red=196
    local yellow=226
    local green=46
    local darkgreen=28
    if [ $retval -eq 0 ]; then
        color=$green
        face=":-)"
    elif [ $retval -eq 1 ]; then
        color=$red
        face=":-("
    elif [ $retval -eq 130 ]; then # INT
        color=$yellow
        face=":-|"
    elif [ $retval -eq 132 ]; then # ILL
        color=$darkgreen
        face=":-&"
    elif [ $retval -eq 137 ]; then # KILL
        color=$red
        face="X_X"
    elif [ $retval -eq 139 ]; then # SEGV
        color=$red
        face=">_<"
    elif [ $retval -eq 143 ]; then # TERM
        color=$red
        face="x_x"
    else
        color=$red
        face="O_o"
    fi
    echo -e "\001$(tput setaf $color; tput bold)\002$face\001$(tput sgr0)\002"
    return $retval # preserve the value of $?
}
PS1="$PS1\$(prompt_smile) "

Download

Which looks like this:

bash-4.4$ . promptsmile-3wide.sh
bash-4.4$ :-) false
bash-4.4$ :-( true
bash-4.4$ :-) sleep 60 & X=$!; (sleep 1; kill -INT $X) & fg %1
[1] 26914
[2] 26915
sleep 60

[2]+  Done                    ( sleep 1; kill -INT $X )
bash-4.4$ :-| sleep 60 & X=$!; (sleep 1; kill -ILL $X) & fg %1
[1] 26925
[2] 26926
sleep 60
Illegal instruction (core dumped)
[2]   Done                    ( sleep 1; kill -ILL $X )
bash-4.4$ :-& sleep 60 & X=$!; (sleep 1; kill -KILL $X) & fg %1
[1] 26991
[2] 26992
sleep 60
Killed
[2]+  Done                    ( sleep 1; kill -KILL $X )
bash-4.4$ X_X sleep 60 & X=$!; (sleep 1; kill -SEGV $X) & fg %1
[1] 27001
[2] 27002
sleep 60
Segmentation fault (core dumped)
[2]   Done                    ( sleep 1; kill -SEGV $X )
bash-4.4$ >_< sleep 60 & X=$!; (sleep 1; kill -TERM $X) & fg %1
[1] 27065
[2] 27066
sleep 60
Terminated
[2]+  Done                    ( sleep 1; kill -TERM $X )
bash-4.4$ x_x (exit 4)
bash-4.4$ O_o true
bash-4.4$ :-) exit

For that old-school style, the text-based emoticons work well, but systems that support emojis are becoming rather common-place, so we can use UTF-8 to get little emotional faces in our prompts:

#!/bin/bash
# source this
function prompt_emoji () {
    local retval=$?
    local red=196
    local yellow=226
    local green=46
    local darkgreen=28
    if [ $retval -eq 0 ]; then
        color=$yellow
        face=$'\360\237\230\200' # :D
    elif [ $retval -eq 1 ]; then
        color=$yellow
        face=$'\360\237\230\246' # :(
    elif [ $retval -eq 130 ]; then # INT
        color=$yellow
        face=$'\360\237\230\220' # :|
    elif [ $retval -eq 132 ]; then # ILL
        color=$darkgreen
        #face=$'\360\237\244\242' # nauseated # get a rectangle in Konsole
        face=$'\360\237\230\223' # cold sweat face
    elif [ $retval -eq 137 ]; then # KILL
        color=$yellow
        face=$'\360\237\230\265' # x_x
    elif [ $retval -eq 139 ]; then # SEGV
        #color=$yellow
        #face=$'\360\237\244\250' # Face with one eyebrow raised # get a rectangle in Konsole
        color=$red
        face=$'\360\237\230\240' # Angry face
    elif [ $retval -eq 143 ]; then # TERM
        color=$yellow
        face=$'\360\237\230\243' # >_<
    else
        color=$yellow
        face=$'\360\237\230\245' # ;(
    fi
    echo -e "\001$(tput setaf $color; tput bold)\002$face\001$(tput sgr0)\002"
    return $retval # preserve the value of $?
}
PS1="$PS1\$(prompt_emoji) "

Download

Which will give something maybe like this. The way the faces are rendered will depend on your terminal. For Konsole, these are simple line art; for Gnome-terminal some match Konsole, others have a more blob-like shape; and here they're rendered by your browser.

bash-4.4$ . promptemoji.sh
bash-4.4$ 😀 false
bash-4.4$ 😦 true
bash-4.4$ 😀 sleep 60 & X=$!; (sleep 1; kill -INT $X) & fg %1
[1] 27143
[2] 27144
sleep 60

[2]+  Done                    ( sleep 1; kill -INT $X )
bash-4.4$ 😐 sleep 60 & X=$!; (sleep 1; kill -ILL $X) & fg %1
[1] 27154
[2] 27155
sleep 60
Illegal instruction (core dumped)
[2]   Done                    ( sleep 1; kill -ILL $X )
bash-4.4$ 😓 sleep 60 & X=$!; (sleep 1; kill -KILL $X) & fg %1
[1] 27220
[2] 27221
sleep 60
Killed
[2]+  Done                    ( sleep 1; kill -KILL $X )
bash-4.4$ 😵 sleep 60 & X=$!; (sleep 1; kill -SEGV $X) & fg %1
[1] 27232
[2] 27233
sleep 60
Segmentation fault (core dumped)
[2]   Done                    ( sleep 1; kill -SEGV $X )
bash-4.4$ 😠 sleep 60 & X=$!; (sleep 1; kill -TERM $X) & fg %1
[1] 27295
[2] 27296
sleep 60
Terminated
[2]+  Done                    ( sleep 1; kill -TERM $X )
bash-4.4$ 😣 (exit 4)
bash-4.4$ 😥 true
bash-4.4$ 😀 exit

Beyond that, you'll find that your terminal may render a different subset of emojis than what mine does. I found a useful site for finding emojis with octal UTF-8 which makes it easy to update promptemoji.sh with something that suits your particular set of software.

And for ANSI colors, you may find this reference handy.

Go then, and liven up your own bash prompts, even more!

Having fun with the bash prompt

Run Time

I frequently want to know how long a command took, but only after I realize that it's taking longer than I expected. So I modified my bash prompt to time every single command I run. So each prompt looks like

[eli@hostname blog]$ [13]

When a command takes a long time, I may want to go work on something else for a couple of minutes, but still want to know when it completes. So I made the command prompt include a bell character and an exclamation point if the command exceeded 5 seconds.

[eli@hostname blog]$ [13!]

It would also be nice to have my eye drawn to the prompt if it took a long time, but I don't want to be distracted by all the [0]'s getting displayed. So I made the color vary based on the length of time. If it is 0 seconds, it's displayed in black, and as it takes longer, it transitions to white, and then to increasingly brighter shades of red, maxing out at bright red on a 5 minute run time.

[eli@hostname blog]$ [0s] sleep 5
[eli@hostname blog]$ [5s!] sleep 60
[eli@hostname blog]$ [60s!] sleep 252
[eli@hostname blog]$ [252s!] 
[eli@hostname blog]$ [0s] sleep 1
[eli@hostname blog]$ [1s] 
[eli@hostname blog]$ [0s] 

So all that code went into a dotscript I called promptautobell:

#!/bin/bash
function prompt_autobell_start {
    prompt_autobell_timer=${prompt_autobell_timer:-$SECONDS}
}
function prompt_autobell_stop {
    local retval=$?
    prompt_autobell_elapsed=$(($SECONDS-$prompt_autobell_timer))
    unset prompt_autobell_timer

    local color
    local bell
    if [ $prompt_autobell_elapsed -ge 5 ]; then
        bell="\001\a\002!"
    else
        bell=""
    fi
    
    color="$(tput bold)$(tput setaf $((prompt_autobell_elapsed > 300 ? 196 \
: prompt_autobell_elapsed > 22 ? 16+(1+(prompt_autobell_elapsed-1)/60)*36: \
233+prompt_autobell_elapsed)))"

    prompt_autobell_show="$(echo -e "\001${color}\002[\
${prompt_autobell_elapsed}s$bell]\001$(tput sgr0)\002")"

    return $retval
}
trap 'prompt_autobell_start' DEBUG
PROMPT_COMMAND="prompt_autobell_stop"
PS1="$PS1\${prompt_autobell_show} "

So that's nice, we get to know how long our commands take, and automatically get nudged when a long-running command finally completes.

Return Codes

But that doesn't tell us if the command passed or failed. And in the *NIX tradition, commands are generally silent on success. So what if we make the prompt display an appropriate emoticon based on the exit code? Like, a green smiley on success, and a red frown on failure. And maybe a few other expressions as well.

[eli@hostname blog]$ source ~/bin/promptsmile
[eli@hostname blog]$ :) 
[eli@hostname blog]$ :) false
[eli@hostname blog]$ :( sleep 60
^C
[eli@hostname blog]$ :| sleep 60
Terminated
[eli@hostname blog]$ x_x sleep 60
Segmentation fault (core dumped)
[eli@hostname blog]$ >_< true
[eli@hostname blog]$ :) 

So into a dotscript called promptsmile goes:

#!/bin/bash
# source this
function prompt_smile () {
local retval=$?
if [ $retval -eq 0 ]; then
    color=46
    face=":)"
elif [ $retval -eq 1 ]; then
    color=196
    face=":("
elif [ $retval -eq 130 ]; then # INT
    color=226
    face=":|"
elif [ $retval -eq 139 ]; then # SEGV
    color=196
    face=">_<"
elif [ $retval -eq 143 ]; then # TERM
    color=196
    face="x_x"
else
    color=196
    face=";("
fi
echo -e "\001$(tput setaf $color; tput bold)\002$face\001$(tput sgr0)\002"
return $retval # preserve the value of $?
}
PS1="$PS1\$(prompt_smile) "

Note that the emoticon logic is readily extensible. Do you frequently deal with a program that has a couple of special exit codes? Make those stand out with a bit of straight-forward customization of the prompt_smile function.

Combined

And of course, I want an easy way to get both of these behaviors at the same time, so I created a dotscript called promptfancy:

source ~/bin/promptautobell
source ~/bin/promptsmile

And to make it easy to apply to a shell, I added to ~/.bashrc:

alias fancyprompt="source ~/bin/promptfancy"

And now,

[eli@hostname blog]$ fancyprompt 
[eli@hostname blog]$ [0s] :) 
[eli@hostname blog]$ [0s] :) sleep 60
^C
[eli@hostname blog]$ [1s] :| sleep 60
Terminated
[eli@hostname blog]$ [12s!] x_x sleep 60
Segmentation fault (core dumped)
[eli@hostname blog]$ [12s!] >_< false
[eli@hostname blog]$ [0s] :( sleep 6
[eli@hostname blog]$ [6s!] :) 
[eli@hostname blog]$ [0s] :) 
[eli@hostname blog]$ [0s] :) 

Go then, and liven up your own bash prompts!