I'm trying to create a shell prompt that displays the $PWD in a "smart" way. I want to display the top two directories rather than the full path. If $HOME is the parent (or parent of the parent) directory, I want to show it like ~.

Assuming my $HOME is "/home/user", here are a few test cases with expected output according to several $PWD values:

/                  -> /
/home              -> /home
/home/user         -> ~
/home/user/foo     -> ~/foo
/home/user/foo/bar -> foo/bar
/usr/local         -> /usr/local
/usr/local/games   -> local/games
/home/userr        -> /home/userr

Currently, my solution is as follow:


# $n is the number of "/" in the path indicating that there is more than 2 dirs
# In such case, the path should be truncated
# If the path contains root / (like "/home/usr"), 3 "/" implies 3 dirs 
# If the path is "~/foo" then only 2 "/" are required
# If ${PWD} starts with ${HOME}, we can replace this part with "~"
# A slash need to be appended to "${HOME}" to avoid false positive
if [[ ${path}/ == ${HOME}/* ]]; then

# Count the number of "/" in the path
# If there is more than $n "/", this means the path contains more than 2 dirs
# In this situation, we need to extract the two last dirs only from the path
if [[ ${#slashes} -ge $n ]]; then

echo $path

This function is intended to be used in my bash shell prompt. As this will be issued each time I type a command in my terminal, and as I will keep this in my .bashrc for probably several years to come, I'm looking to "optimize" this function.

Could the solution have been implemented otherwise, or could this function be improved in some way in order to minimize it's overall "footprint"?


Returning values by variable is 3-10 times faster than capturing an echo with $( ). Your temp variable is already global. Just name it something similar to what the surrounding function is named.

Whitespace and comments affect bash performance negatively. It's not a big penalty, just something to be aware of.

A regular expression can do the "last two path components" extraction very compactly.

cwd() {
  [[ $cwd/ == $HOME/* ]] && cwd=~${cwd#$HOME}
  [[ $cwd =~ ./([^/]+/[^/]+)$ ]] && cwd=${BASH_REMATCH[1]}


PS1="\$cwd \$"

I'd say not to count the tilde as a path component: it's only one extra character that carries a lot of information, well worth the single column. The regex is then:

  [[ $cwd =~ (..|[^~])/([^/]+/[^/]+)$ ]] && cwd=${BASH_REMATCH[2]}
| improve this answer | |
  • \$\begingroup\$ Thanks for the tips. I have figured out that best performances are reached by avoiding use of regex engine (and replacing ${HOME}/* with index-based substring extraction). \$\endgroup\$ – Delgan May 2 '19 at 23:18

Based on @OhMyGoodness suggestions, here is an improved function:

cwd() {
    [[ $cwd/ == $HOME/* ]] && cwd=~${cwd:${#HOME}}
    begin=${cwd%?/*/*} && [[ "$begin" != "$cwd" ]] && path=${cwd:${#begin}+2}

It's slightly faster as it reduces string manipulations.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.