• mina86.com

  • Categories
  • Code
  • Contact
  • Bash right prompt

    Posted by Michał ‘mina86’ Nazarewicz on 28th of September 2015

    There are multiple ways to customise Bash prompt. There’s no need to look for long to find plethora of examples with fancy, colourful PS1s. What have been a bit problematic is having text on the right of the input line. In this article I’ll try to address that shortcoming.

    Getting text on the right

    The typical approach is using PROMPT_COMMAND to output desired content. The variable specifies a shell code Bash executes prior to rendering the primary prompt (i.e. PS1).

    The idea is to align text to the right and then using carrier return move the cursor back to the beginning of the line where Bash will start rendering its prompt. Let’s look at an example of showing time in various locations:

    __command_rprompt() {
    	local times= n=$COLUMNS tz
    	for tz in ZRH:Europe/Zurich PIT:US/Eastern \
    	          MTV:US/Pacific TOK:Asia/Tokyo; do
    		[ $n -gt 40 ] || break
    		times="$times ${tz%%:*}\e[30;1m:\e[0;36;1m"
    		times="$times$(TZ=${tz#*:} date +%H:%M)\e[0m"
    		n=$(( $n - 10 ))
    	done
    	[ -z "$times" ] || printf "%${n}s$times\\r" ''
    }
    PROMPT_COMMAND=__command_rprompt
    Terminal window presenting right prompt behaviour.

    Clearing the line on execution

    It has one annoying issue. The right text reminds on screen even after executing a command. Typically this is a matter of aesthetic but it also makes copying and pasting session history more convoluted.

    A manual solution is to use redraw-current-line readline function (e.g. often bound to C-l). It clears the line and prints the prompt and whatever input has been entered thus far. PROMPT_COMMAND is not executed so the right text does not reappear.

    Lack of automation can be addressed with a tiny bit of readline magic and a ~/.inputrc file which deserves much more fame than what it usually gets.

    Tricky part is bindind C-m and C-j to two readline functions, redraw-current-line followed by accept-line, which is normally not possible. This limitation can be overcome by binding the key sequences to a different sequence which will be interpreted recursively.

    To test that idea it’s enough to execute:

    bind '\C-l:redraw-current-line'
    bind '\M-\C-j:accept-line'
    bind '\C-j:"\C-l\M-\C-j"' '\C-m:"\C-j"'

    Making this permanent is as easy as adding the following lines to ~/.inputrc:

    $if Bash
        "\C-l": redraw-current-line
        "\e\C-j": accept-line
        "\C-j": "\C-l\e\C-j"
        "\C-m": "\C-l\e\C-j"
    $endif

    With that, the right prompt will disappear as soon as the shell command is executed. (Note the use of \M- in bind command vs. \e in ~/.inputrc file).

    Caveats

    Even though improvement over naïve PROMPT_COMMAND solution, the aforementioned method has minor downsides. Most noticeably, the standard prompt and entered text may clash with the text on the right. Furthermore, deleting characters from the middle of the entered line shifts the right prompt by one character.

    The latter can be addressed with similar way as accept-line but even without a solution the two issues are rather minor and should bother only the most pedantic.

    Post Scriptum

    Dear Zed Shell users. I know zsh has RPROMPT. I’ve tried this shell and didn’t like it.