command line - Bash prompt: how to have the initials of directory path

05
2013-12
  • Hamish Downer

    I normally have just the name of the current directory in my bash prompt (PS1='\u@\h:\W$ '), so if I am in ~/projects/superapp/src/ I get:

    hamish@host:src$ 
    

    However I'd like to have an indication of the full path without having the full path. I've seen screenshots where people would have

    hamish@host:~/p/s/src$ 
    

    if in the example directory above. So what value of PS1 would give that? Or failing that, what script do I need in my .bashrc to produce that?

  • Answers
  • Telemachus

    Ok, I got curious, so here's one solution:

    1. First, create a function using a slight tweak of William Pursell's answer to the SO question I link in my comment above.
    2. Next, put that in your $PS1 as \$(function_name) in the appropriate place.

    As an example:

    short_pwd() {
        cwd=$(pwd | perl -F/ -ane 'print join( "/", map { $i++ < @F - 1 ?  substr $_,0,1 : $_ } @F)')
        echo -n $cwd
    }
    # later in your .bashrc
    PS1="\u \$(short_pwd) \$ "
    

    I'm hopeful that someone more skilled in Bash-scripting than I am can suggest ways to clean up the function, but this should give you some idea of how to use the output of another command (or Bash function) in a prompt. See also here: http://www.linux.org/docs/ldp/howto/Bash-Prompt-HOWTO/x279.html

    Based on your comment, I looked again and realized that my solution needs to be double quoted. If you single-quote such a function, then it will not function at all.

  • meowsqueak

    I rewrote this recently, based on another script I wrote years ago - this one is optimised to run inside bash as much as possible, to avoid costly forks. It was almost 8x faster than my old function that used awk/sed.

    It does produce nice results. It keeps the pwd part of the prompt to no more than MAX_PWD_LENGTH characters, and if you're in a subdir of $HOME, it makes this clear too:

    Examples:

    pc770-ubu:~ $ cd ~/a/b/c
    pc770-ubu:~/a/b/c $ cd d/e/f
    pc770-ubu:~/a/b/c/d/e/f $ cd g
    pc770-ubu:~/a/b/c/d/e/f/g $ cd h
    pc770-ubu:~/a/b/c/d/e/f/g/h $ cd i
    pc770-ubu:~/a/b/c/d/e/f/g/h/i $ cd j
    pc770-ubu:~/a/b/c/d/e/f/g/h/i/j $ cd k
    pc770-ubu:~/a/b/c/d/e/f/g/h/i/j/k $ cd l
    pc770-ubu:~../c/d/e/f/g/h/i/j/k/l $ cd m
    pc770-ubu:~../d/e/f/g/h/i/j/k/l/m $ cd n
    pc770-ubu:~../e/f/g/h/i/j/k/l/m/n $ cd o
    pc770-ubu:~../f/g/h/i/j/k/l/m/n/o $ cd /tmp/a/b/c/d/e/f
    pc770-ubu:/tmp/a/b/c/d/e/f $ cd g
    pc770-ubu:/tmp/a/b/c/d/e/f/g $ cd h
    pc770-ubu:/tmp/a/b/c/d/e/f/g/h $ cd i
    pc770-ubu:/tmp/a/b/c/d/e/f/g/h/i $ cd j
    pc770-ubu:/../a/b/c/d/e/f/g/h/i/j $ cd k
    pc770-ubu:/../b/c/d/e/f/g/h/i/j/k $ cd l
    pc770-ubu:/../c/d/e/f/g/h/i/j/k/l $ cd m
    pc770-ubu:/../d/e/f/g/h/i/j/k/l/m $ cd
    pc770-ubu:~ $ 
    

    The bash function (call this when constructing your PS1 variable):

    # set this to whatever you want:
    MAX_PWD_LENGTH=20
    
    function shorten_pwd
    {
        # This function ensures that the PWD string does not exceed $MAX_PWD_LENGTH characters
        PWD=$(pwd)
    
        # if truncated, replace truncated part with this string:
        REPLACE="/.."
    
        # determine part of path within HOME, or entire path if not in HOME
        RESIDUAL=${PWD#$HOME}
    
        # compare RESIDUAL with PWD to determine whether we are in HOME or not
        if [ X"$RESIDUAL" != X"$PWD" ]
        then
            PREFIX="~"
        fi
    
        # check if residual path needs truncating to keep total length below MAX_PWD_LENGTH
        # compensate for replacement string.
        TRUNC_LENGTH=$(($MAX_PWD_LENGTH - ${#PREFIX} - ${#REPLACE} - 1))
        NORMAL=${PREFIX}${RESIDUAL}
        if [ ${#NORMAL} -ge $(($MAX_PWD_LENGTH)) ]
        then
            newPWD=${PREFIX}${REPLACE}${RESIDUAL:((${#RESIDUAL} - $TRUNC_LENGTH)):$TRUNC_LENGTH}
        else
            newPWD=${PREFIX}${RESIDUAL}
        fi
    
        # return to caller
        echo $newPWD
    }
    

    EDIT: fixed bug with absolute string length

  • leff

    I like meowsqueak's approach, trying to stay in bash for performance. But I wanted my path to abbreviate long directory names down to one char.

    me@comp:~ $ cd my/path/haslongnames/
    me@comp:~my/p/h $
    

    This is based on meowsqueak's solution. It could stand some improvements/more features but it solves the basic problem without firing up sed.

    This is in an executable file, for instance ~/bin/ps1

    # set this to whatever you want:
    MAX_PWD_LENGTH=30
    function shorten_pwd
    {
        # This function ensures that the PWD string does not exceed $MAX_PWD_LENGTH characters
        PWD=$(pwd)
    
        # determine part of path within HOME, or entire path if not in HOME
        RESIDUAL=${PWD#$HOME}
    
        # compare RESIDUAL with PWD to determine whether we are in HOME or not
        if [ X"$RESIDUAL" != X"$PWD" ]
        then
            PREFIX="~"
        fi
    
        # check if residual path needs truncating to keep total length below MAX_PWD_LENGTH
        NORMAL=${PREFIX}${RESIDUAL}
        if [ ${#NORMAL} -ge $(($MAX_PWD_LENGTH)) ]
        then
            newPWD=${PREFIX}
            OIFS=$IFS
            IFS='/'
            bits=$RESIDUAL
            for x in $bits
            do
                if [ ${#x} -ge 3 ]
                then
                    NEXT="/${x:0:1}"
                else
                    NEXT="$x"
                fi
                newPWD="$newPWD$NEXT"
            done
    
            IFS=$OIFS
        else
            newPWD=${PREFIX}${RESIDUAL}
        fi
    
        # return to caller
        echo $newPWD
    }
    export PS1="\u@\h:$(shorten_pwd) $ "
    

    In my .bash_profile I then have

    PROMPT_COMMAND="source $HOME/bin/ps1"
    
  • cYrus

    Just add this (and edit as you like) to your .bashrc:

    PS1='\u@\h:`pwd | sed -e "s/\/\(.\)[^\/]\+/\/\1/g"`\$ '
    
  • Hamish Downer

    I've done a little more googling in the meantime, and after going through a few search terms, I came across this article that mentions the fish shell does what I want and provided a way of doing it. I modified it so the user and host are also displayed and ended up with the reasonably succinct:

    # abbreviate the dir path
    PROMPT_COMMAND='CurDir=`pwd|sed -e "s!$HOME!~!"|sed -re "s!([^/])[^/]+/!\1/!g"`'
    PS1="\u@\h:\$CurDir \$ "
    

    Basically every time the prompt is about to be displayed, PROMPT_COMMAND will set $CurDir to the abbreviated directory path which is then used in $PS1. Bare in mind that if PROMPT_COMMAND is set elsewhere you will need to add the above command on to the end of that one, preceded by a ;. So for the common example of setting the title of an xterm you would end up with

    PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD/$HOME/~}\007"; CurDir=`pwd|sed -e "s!$HOME!~!"|sed -re "s!([^/])[^/]+/!\1/!g"`'
    

    Some other possible ways of abbreviating the path can be found:

  • Daniel Beecham

    Another version of @Telemachus short_pwd(), without perl requirement.

    short_pwd() {
    cwd=$(pwd)
    
    if [ $cwd == $HOME ]; then echo -n "~"; return; fi 
    if [ $cwd == "/" ]; then echo -n "/"; fi 
    
    for l in $(echo $cwd | tr "/" "\n"); do 
        echo -n "/"
        echo -n ${l:0:1}
    done
    echo -n ${l:1}
    }
    
  • Dmitry Leskov
    PROMPT_DIRTRIM=3
    

    will force \w to expand to the maximum of three trailing elements of the current working directory path, with the preceding, if any, replaced with "...".


  • Related Question

    linux - Show only current directory name (not full path) on bash prompt
  • obvio171

    The way my bash prompt is currently configured, it shows the whole path to the current directory. This is annoying when I'm deep inside a directory tree, as the prompt becomes so long that every command wraps into the next line. How do I make it show only the last part of the path?

    This is what I have in my .bashrc:

    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
    
    # If this is an xterm set the title to user@host:dir
    case "$TERM" in
    xterm*|rxvt*)
        PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD/$HOME/~}\007"'
        ;;
    *)
        ;;
    esac
    

  • Related Answers
  • quack quixote

    Change the \w (lowercase) to \W (uppercase):

    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\W\[\033[00m\]\$ '
                                                                                           ^^
               this one waaaaaay over here ------------------------------------------------+
    

    Have a look at the Bash Prompt HOWTO for lots of fun details. example:

    user@host:/usr/local/bin$ echo $PS1
    ${debian_chroot:+($debian_chroot)}\[\033[01;31m\]\u@\h\[\033[00m\]:\[\033[01;36m\]\w\[\033[00m\]\$ 
    
    user@host:/usr/local/bin$ export PS1='${debian_chroot:+($debian_chroot)}\[\033[01;31m\]\u@\h\[\033[00m\]:\[\033[01;36m\]\W\[\033[00m\]\$ '
    
    user@host:bin$
    

    The PROMPT_COMMAND variable, if set, is a command that gets run before displaying the prompt specified in PS1. In your case, PROMPT_COMMAND runs an echo statement with certain ANSI escape sequences that manipulate the titlebar of an Xterm.

    If you suspect your PROMPT_COMMAND is overriding your PS1 prompt, you can unset it and test things out:

    $ unset PROMPT_COMMAND
    

    Finally, be sure that you're changing the PS1 definition that actually gets used. Common locations are /etc/bash.bashrc, /etc/profile, ~/.bashrc, ~/.bash_profile, ~/.profile. The system files are generally (but not always) run before the user files.