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:
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
}