Bash Prompt Experimentation

Something that's fascinated me for years is how to pack as much information as possible into the Bash Prompt. I just (re-)discovered some work I was doing back in 2013. I cooked up two separate ways to create a prompt that's either one or two lines depending on the amount of information being displayed. Both work, although the approaches are very different.

# onetwo
#
# the line with "acpi" is a battery power indicator that should work
# on Linux, but won't work on a Mac.  Simply remove that line if it
# doesn't work.

rollover=14

function prompt_command () {
    dir="${PWD/${HOME}/\~}"
    if [ ${#dir} -ge ${rollover} ]
    then
        export break="
"
    else
        export break=" "
    fi
}

PROMPT_COMMAND=prompt_command

export PS1="\${dir}\${break}\
(\$(acpi | awk '{print \$4}' | tr -d ',')) \
\u@\h\[\e[1;33m\]$\[\e[0m\] "

If you have a function called PROMPT_COMMAND in the environment it's executed just before the prompt is, so you can set variables and make decisions in it. Save the above file as onetwo and then just source onetwo to make it your current prompt.

See below to see what both of them look like.

In the above prompt, we make the decision about when to change from a one- to two-line prompt based entirely on the width of ${PWD} (or more correctly, the width of ${PWD/${HOME}/\~), which substitutes a ~ for your home directory) - the current working directory.

A better way to make that decision would be to base it on the space remaining after the prompt: if I have a single line prompt that includes a very long directory name, I find it very irritating when I type five characters and the text wraps to the next line because that's all the space left on the line. So I worked on figuring out the remaining space. To do this, we put active - decision-making - code into the ${PS1} variable. If you're inside $PS1 you can grab interesting information such as the width of prompt substitutions like \u@\h (which displays <username>@<hostname> or in my case giles@toshi7). This isn't possible in the $PROMPT_COMMAND because those escape sequences don't exist except inside PS1.

# onetwofill
# no battery power indicator, shows git status when in a repository
# folder.  Should work on Mac or Linux.

p_minRemainingWidth=50

  YELLOW='\[\e[1;33m\]'
   WHITE='\[\e[1;37m\]'
   NOCOL='\[\e[0m\]'

prompt_command () {
    if git status &> /dev/null
    then
        gitBranch="$(git branch | awk '{ print $2 }')"
        if [[ "$(git status)" != *" clean"* ]]
        then
            gitStatus="!"
        else
            gitStatus=""
        fi
    else
        gitBranch=""
        gitStatus=""
    fi
    p_displayGit="${gitBranch}${gitStatus}"
    p_dir="${PWD/${HOME}/\~}"
}

PROMPT_COMMAND=prompt_command

export PS1="\$(

remainingWidth=\$(( COLUMNS - \${#p_dir} - \${#p_displayGit} - \$(echo "\u@\h" | wc -c) - 3 ))

if [ \${remainingWidth} -le ${p_minRemainingWidth} ] ;
then
    fillWidth=\$(( COLUMNS - \${#p_dir} - \${#p_displayGit} ))
    fill=\"\";
    for (( i=0 ; i<\${fillWidth} ; i=i+1))
    do
        fill=\"\${fill} \"
    done
    echo -e \"\${p_dir}\${fill}\${p_displayGit}\n\u@\h${YELLOW}\$${NOCOL} \";
else
    echo -en \"\${p_dir}\"
    if [ \"\${p_displayGit}x\" != \"x\" ]
    then
        echo -en \"|\${p_displayGit}\"
    fi
    echo -e \"|\u@\h${YELLOW}\$${NOCOL} \";
fi
)"

You can do a lot in the PS1 string, but the more you do the more you have to escape your '$' and '"' (double quotes) - it's a problem. Move as much functionality as you can to the PROMPT_COMMAND. I've removed the power indicator in this prompt and added git branch and status (if the folder you're in is a repository).

giles@toshi7:/home/giles/tmp
2017-08-14:0906$ source onetwo
~/tmp (100%) giles@toshi7$ cd ~/Documents/BashPrompt/reallydeep/directory/structure/
~/Documents/BashPrompt/reallydeep/directory/structure
(100%) giles@toshi7$ cd -
/home/giles/tmp
~/tmp (100%) giles@toshi7$ source onetwofill
~/tmp|giles@toshi7$ cd -
/home/giles/Documents/BashPrompt/reallydeep/directory/structure
~/Documents/BashPrompt/reallydeep/directory/structure
giles@toshi7$ cd ~/bin
~/bin|master|giles@toshi7$