bash
prompt¶Under “PROMPTING”, the bash
manual describes two codes that can be added to the PS1
variable to insert information about the current working directory into your prompt:
\w
- the current working directory, with
$HOME
abbreviated with a tilde\W
- the basename of the current working directory, with
$HOME
abbreviated with a tilde
\w
is annoying because it shows the complete path from the root of the directory tree to your current location - if you have deeply nested directory structures (and once you start doing actual work on a decent-sized project you probably will) your prompt can get absurdly long.
\W
is annoying because it removes all the context from your current location - if you have several projects lying around and they all have a “docs
” subdirectory, you’ve got no way of telling them apart without having to run some other command.
Wouldn’t it be good if there was a way to have your prompt show you a level of detail in between “everything” and “nothing”?
Because \w
and \W
are special codes interpreted by bash
‘s prompt code, you can’t work on them like ordinary shell variables. One alternative might be to use command expansion with pwd
, or (on linux) clever introspection via /proc/self
, but it turns out there’s an easier way: bash keeps a record of the current working directory in the magic variable PWD
. I call it “magic” because the shell keeps it up-to-date without you having to manually set it every time. See “Shell Variables” in the bash
manual for other magic variables that bash maintains.
What we really want here is something like Python’s string slicing, where you can say “give me the last n characters of this variable”. Looking at the “Parameter Expansion” section of the bash
manual, we discover Substring Expansion:
${parameter:offset}
${parameter:offset:length}
- Expands to up to length characters of parameter starting at the character specified by offset. If length is omitted, expands to the substring of parameter starting at the character specified by offset. […] If offset evaluates to a number less than zero, the value is used as an offset from the end of the value of parameter.
Sounds great, let’s try it!
$ echo $PWD
/home/st
$ echo ${PWD: -4}
e/st
$ _
It works! Or does it…
$ echo ${PWD: -10}
$ _
It turns out that if you have a string n characters long, asking for the last (n-1) characters will give you (n-1) characters, asking for the last (n) characters will give you (n) characters, but asking for (n+1) or more characters will give you nothing.
We can get around this by using a temporary variable and the Use Default Values exansion:
${parameter:-word}
- If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter is substituted.
(clever readers will note that the Substring Expansion examples above very carefully left a space between the “:” and the negative number, so the shell wouldn’t get them confused with Use Default Values expansion)
Here’s our workaround. First, we grab the last however-many characters (10 in this case) if we can:
$ TEMP_PWD=${PWD: -10}
If PWD
was less than 10 characters long, TEMP_PWD
will be empty.
If TEMP_PWD
is empty, we know PWD
was already short enough and we can just set TEMP_PWD
to PWD
. Otherwise (TMP_PWD
is not empty), we must have trimmed something and we can use it as-is:
$ TEMP_PWD=${TEMP_PWD:-$PWD}
$ echo $TEMP_PWD
/home/st
$ _
We have some commands we can run to set up a variable that contains the data we want to display in our prompt, but we need to recalculate it every time the user changes directory. bash
doesn’t give us useful event-handlers like that, but under “Shell Variables”, the bash
manual describes the PROMPT_COMMAND
variable, which can be set to a sequence of commands to be run every time bash
is about to display a prompt.
There’s one final trick to setting a custom prompt: if you just set PS1
to include a variable directly, it probably won’t do what you expect:
$ PS1="You are in $PWD: "
You are in /home/st: pwd
/home/st
You are in /home/st: cd /
You are in /home/st: pwd
/
You are in /home/st: _
This is because PWD
was expanded at the time you set PS1
- if you want the prompt to include the contents of PWD
at the time the prompt is displayed, you have to stop bash
from expanding the variables at the time you set PS1
. The easiest way is just to use single-quotes instead of double-quotes:
$ PS1='You are in $PWD: '
You are in /Users/st: cd /
You are in /: _
Here’s what I have in my ~/.bashrc
that uses all the things mentioned above, plus a few more that should be adequately described in the comments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Note that I’ve boosted the trim length up to 40 characters (half the width of a standard terminal window), and introduced some logic to extend PROMPT_COMMAND
if it’s already set. The simple thing to do would have been:
PROMPT_COMMAND="$PROMPT_COMMAND; new prompt command"
…but if PROMPT_COMMAND
is not already set, that gives you a PROMPT_COMMAND
beginning with a semicolon, and certain versions of bash
complain bitterly about such things.