Prompt me

I love minimalism. I had my prompt long time just beeing a simple status indicator and a pipe, everything which was not that important was ripped out.

At some point, I decided that I need at least the time in my prompt, but I wanted it to be on the right side. I managed to do it. And now, as I have more machines, I need to put the hostname in the prompt. I also added a shell-level indicator in the prompt, a “How many jobs are in the background” indicator was also added some time ago.

This post wants to elaborate how this all was achived while keeping the prompt itself as minimal as possible. At least from my point of view.

Requirements

So first, the requirements:

  • Indicators
    • Did the last command work?
    • How many background jobs do you have?
    • How many shell levels are there?
  • What’s the current hostname?
  • What’s the time?
  • Where am I?

Also, the bash shouldn’t flatter around. I want to have my commands in one column, straight down the line.

The prompt

So, that’s what my prompt looks like:

my prompt

As you can see, my hostname is one the left but almost everything else is on the right. Well, there’s actually really much on the left!

First, there is the hostname. I managed to get the hostname into a box which is always 8 characters long. So longer host names will fit in there as well and don’t break the pipe bar at the end of the left side of the prompt.

Also, the hostname will be green whenever the last command succeeded, turning into red if the last command failed.

After the hostname we have the pipe, which is simply the delimiter between my hostname and my commands. Well, not quite. It also doubles itself in a subshell, tribles itself in a sub-subshell and so on.

On the right side there is another pipe, but this one flutters. After that, we have another two delimiters which could be removed, but I keep them. Then there is the current branch whenever I’m inside a git directory, the current working directory the number of jobs and the time.

Howto

To achive this, you need to write a bit of bash code into your .bashrc, that’s all:

#
# ~/.bashrc
#

printfnchr() {
    for i in `seq 1 $1`; do echo -ne "$2"; done
}

shlvl() {
    # I'm always on shell level 4 here, login, xserver, terminal, bash
    # so subtract 3 to get 1 in a normal (non-nested) shell
    echo $((SHLVL - 3))
}

PS1=$'$(if [[ "$?" == "0" ]];then echo "\[\033[32m\]"; else echo "\[\033[31m\]"; fi) $(printf "%8s" "($HOSTNAME)")\e[0m $(printfnchr $(shlvl) "\u2502") '
PS2=$'\[\033[32m\] $(printf "%8s" "($HOSTNAME)") #>\e[0m \u2502 '

rightpromptstring() {
    local tmp=$?
    local dirty=$([[ $(git diff --shortstat 2>/dev/null | tail -n 1) != "" ]] &&
        echo " *")
    local g=$([[ $(git branch 2>/dev/null) ]] &&
        echo " ($(git describe --all | sed 's/^[a-zA-Z]*\///')) ")
    local p=$(pwd | sed "s:^$HOME:~:" | rev | cut -d/ -f 1 | rev)
    local j=$(jobs | wc -l)
    local d=$(date +%H:%M)

    local r=$(echo -n "\u2502 \033[1;30m<<$dirty$g$p [$j] [$d]\033[0m")

    local rl=${#r}
    local t=$(tput cols)
    let "p = $t - $rl + 22"
    printf -v sp "%*s" $p ' '
    echo -ne "$sp$r\r"
    return $tmp
}

PROMPT_COMMAND=rightpromptstring

It is not as complicated as it seems and I’m sure there’s room for optimization here and there, but for me it works and it is fast.

Of course, the printfnchr function and also the shlvl function could have been placed right in the PS1 variable, but I want to keep them, maybe I can reuse them later!

The rightpromptstring function is a beast, I know. This would be my first point if I had to optimize this whole thing, but I don’t need to, so there is no need to.


I won’t go into detail how things work, I assume you know bash. Anyways, feel free to ask me questions about this!


Edit:

To have the pipe character also when logged in via SSH, you have to modify the shlvl function like this:

shlvl() {
    # I'm always on shell level 4 here, login, xserver, terminal, bash
    # so subtract 3 to get 1 in a normal (non-nested) shell
    if [ -z "$SSH_CLIENT" ]
    then
        echo $((SHLVL - 3))
    else
        echo $SHLVL
    fi
}