musicmatzes blog

r

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
}

tags: #linux #bash

I switched to i3 recently and therefor reconfigured my whole system in manner of apperance. I really like it now, as it uses grey as basic color. Everything is grey: My terminal (xterm, bash), my editor (vim), my IRC client (irssi), my music player (ncmpcpp). But what I like most by far, is my prompt.

Bash it to the right

I always hated the flutter of my bash prompt when diving into directories:

~ $ cd foo ~/foo & cd bar ~/foo/bar $ cd ~ $

But now, I finally reconfigured it to be in a straigt line:

:> | cd foo :> | cd bar :> | cd

And the path is now on the right side (I can't show it in the example, unfortunately).

There is not much magic in it... but let me show you:

The magic

The PS1 variable is the one shelter for the prompt. I configured it to be just

:> |

but if the last command failed, it uses the sad smilie:

:< |

(you can do this with Unicode smilies as well!) There is not much magic in it. The pipe symbol is a Unicode character (\u2502) for me, so I get a straight line without gaps between the lines.

The real magic comes now! There is also a bash variable for a command which gets executed for a prompt. And I abuse this variable a little bit. But first, we have to write the command (actually a bash function):

rightpromptstring() { local tmp=$? local dirty=$([[ $(git diff —shortstat 2>/dev/null | tail -n 1) != “” ]] && echo “ “) local g=$([[ $(git branch 2>/dev/null) ]] && echo “ ($(git name-rev HEAD | cut -d ” “ -f 2)) “) local p=$(pwd | sed “s:^$HOME:~:“) 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” printf -v sp “%s” $p ' ' echo -ne “$sp$r\r” return $tmp }

So, what happens in here? The first line is for saving the return value of the previous command. We need it to be returned lateron, as the smilie from PS1 won't work if we don't return it.

The next few lines do a bit of git magic, to include the current branch and a asterisk if the repository is “dirty”. The variable p gets the path, but if I'm in my home directory, it gets replaced by the tilde. The j variable gets the number of jobs in the bash, as I always want this to be printed somewhere. The d gets the current time.

The r variable now gets the string which represents the right prompt. It also gets the pipe character (actually the Unicode character \u2502) and some color stuff. Then, the rl variable gets the length of the string in r. The t variable gets the number of colums, which is required for printing spaces until we are at the right side (the printf part prints spaces to the variable sp). Then, the spaces and the right prompt get printed. And then simply a \r for carriage-return.

Afterwards we return the stored exit code of the application which just exited and that's it.

Now, just set the prompt command variable:

PROMTP_COMMAND=rightpromptstring

And that's it.

I know, the rightpromptstring function is a bit ugly and I bet it can be improved, but it works very good for me! Feel free to send me patches!

tags: #bash #programming #linux