16

In a system with Ubuntu 14.04 and bash, I have the PS1 variable ending with the following contents:

\u@\h:\w\$

so that the prompt appears as

user@machinename:/home/mydirectory$

Sometimes, however, the current directory has a long name, or it is inside directories with long names, so that the prompt looks like

user@machinename:/home/mydirectory1/second_directory_with_a_too_long_name/my_actual_directory_with_another_long_name$

This will fill the line in the terminal and the cursor will go to another line, which is annoying.

I would like instead to obtain something like

user@machinename:/home/mydirectory1/...another_long_name$

Is there a way to define the PS1 variable to "wrap" and "compact" the directory name, to never exceed a certain number of characters, obtaining a shorter prompt?

16

First of all, you might simply want to change the \w with \W. That way, only the name of the current directory is printed and not its entire path:

terdon@oregano:/home/mydirectory1/second_directory_with_a_too_long_name/my_actual_directory_with_another_long_name $ PS1="\u@\h:\W \$ "
terdon@oregano:my_actual_directory_with_another_long_name $ 

That might still not be enough if the directory name itself is too long. In that case, you can use the PROMPT_COMMAND variable for this. This is a special bash variable whose value is executed as a command before each prompt is shown. So, if you set that to a function that sets your desired prompt based upon the length of your current directory's path, you can get the effect you're after. For example, add these lines to your ~/.bashrc:

get_PS1(){
        limit=${1:-20}
        if [[ "${#PWD}" -gt "$limit" ]]; then
                ## Take the first 5 characters of the path
                left="${PWD:0:5}"
                ## ${#PWD} is the length of $PWD. Get the last $limit
                ##  characters of $PWD.
                right="${PWD:$((${#PWD}-$limit)):${#PWD}}"
                PS1="\[\033[01;33m\]\u@\h\[\033[01;34m\] ${left}...${right} \$\[\033[00m\] "
        else
                PS1="\[\033[01;33m\]\u@\h\[\033[01;34m\] \w \$\[\033[00m\] "
        fi


}
PROMPT_COMMAND=get_PS1

The effect looks like this:

terdon@oregano ~ $ cd /home/mydirectory1/second_directory_with_a_too_long_name/my_actual_directory_with_another_long_name
terdon@oregano /home...th_another_long_name $ 
10

Adding in a character return is my main solution to this

So my prompt (which has other stuff too, making it even longer) looks like this:

enter image description here

You'll notice that the $ is getting returned as a new line

I achieve this with

HOST='\[\033[02;36m\]\h'; HOST=' '$HOST
TIME='\[\033[01;31m\]\t \[\033[01;32m\]'
LOCATION=' \[\033[01;34m\]`pwd | sed "s#\(/[^/]\{1,\}/[^/]\{1,\}/[^/]\{1,\}/\).*\(/[^/]\{1,\}/[^/]\{1,\}\)/\{0,1\}#\1_\2#g"`'
PS1=$TIME$USER$HOST$LOCATION'\n\$ '

Note that, even though on a separate line, super long directory trees such as

/home/durrantm/Dropbox/96_2013_archive/work/code/ruby__rails are shortened to

/home/durrantm/Dropbox/_/code/ruby__rails

i.e. "top 3 directories /_/ bottom two directories" which is usually what I care about

This will make sure that the line never gets too long due to directory tree length. If you always want the full directory tree just adjust LOCATION, i.e.

LOCATION=' \[\033[01;34m\]`pwd`'
  • More on color at unix.stackexchange.com/q/124407/10043 – Michael Durrant Feb 28 '16 at 16:53
  • 1
    Where does your prompt actually include the $ ?  (See this.) – G-Man Jun 17 '18 at 18:28
  • Added the \$ at the end, thanks. it was because normally I also show my git branch but that is too much detail for here – Michael Durrant Jun 17 '18 at 19:35
  • 1
    What?   No newline?   No SGR0? – G-Man Jun 17 '18 at 19:40
  • Added the linefeed – Michael Durrant Jun 17 '18 at 21:49
3

Created ~/.bash_prompt:

maxlen=36
# set leftlen to zero for printing just the right part of the path
leftlen=19
shortened="..."
# Default PWD
nPWD=${PWD}
if [ ${#nPWD} -gt $maxlen ]; then
  offset=$(( ${#nPWD} - $maxlen + $leftlen ))
  nPWD="${nPWD:0:$leftlen}${shortened}${nPWD:$offset:$maxlen}"
else
  nPWD='\w'
fi
echo "\u@\h:$nPWD\$ "

Added in my ~/.bash_profile:

function prompt_command {
  export PS1=$(~/.bash_prompt)
}
export PROMPT_COMMAND=prompt_command

Output is:

user@machinename:/home/mydirectory1/...another_long_name$
1

Not a solution to shorten long paths, but a convenient way to get a better overview while keeping all path information visible, is adding a newline before the last character. This way the cursor starts always in the same column, even if the path is long enough to wrap around, but your console window(s) should be high enough to not scroll out the previous lines too quickly. I removed the color codes for more clarity:

murphy@seasonsend:~
$ echo $PS1
\u@\h:\w\n\$
murphy@seasonsend:~
$ 
1

I use this, it wraps onto multiple lines and indents by the length of user@host so it assumes the current PS1 is effectively '\u@\h:\w$'. It does not truncate the path, and it adapts to the current terminal width. It only splits the path on /, so it doesn't elegantly deal with really long directories (but it does preserve spaces for selection/copy). It makes sure you always have at least a 20 character space available for input.

readonly _PS1="${PS1}" 2>/dev/null

function myprompt()
{
    local IFS
    local nn nb pbits xpwd="" ww=60 len=0 pp='\\w\$ '
    local indent uh="${LOGNAME}@${HOSTNAME//.*/}"

    test -n "$COLUMNS" && let ww=$COLUMNS-20  # may be unset at startup

    PS1="${_PS1}"
    if [ ${#PWD} -ge $ww ]; then
        printf -v indent "%${#uh}s%s" " " "> "  # indent strlen(user@host)

        IFS=/ pbits=( $PWD ); unset IFS
        nb=${#pbits[*]}
        for ((nn=1; nn<nb; nn++)) {
            if [ $(( $len + 1 + ${#pbits[$nn]} )) -gt $ww ]; then
                xpwd="${xpwd}/...\n${indent}..."
                len=0
            fi
            xpwd="${xpwd}/${pbits[$nn]}"
            let len=len+1+${#pbits[$nn]}
        }
        # add another newline+indent if the input space is too tight
        if (( ( ${#uh} + len ) > ww )); then
            printf -v xpwd "${xpwd}\n%${#uh}s" " " 
        fi 
        PS1="${PS1/$pp/$xpwd}$ "    
    fi
}
PROMPT_COMMAND=myprompt

This works by taking the magic \w (matches only \w$ for this) out of PS1 and replacing it with $PWD, then wrapping it as a plain string of characters. It recomputes PS1 each time from the original value which is saved in _PS1, this means "invisible" escapes are preserved too, my full original prompt string for xterm and bold prompt:

PS1="\[\033]0;\u@\h:\w\007\]\[$(tput bold)\]\u@\h\[$(tput sgr0)\]:\w$ "

And the end result in an 80 column terminal:

mr@onomatopoeia:~$ cd /usr/src/linux/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace
mr@onomatopoeia:/usr/src/linux/tools/perf/scripts/perl/Perf-Trace-Util/lib/...
               > .../Perf/Trace$ _

This works from bash-3.2 as printf -v var is used. Due to various complexities it will need some adjustment for other variations of PS1.

(The path in the xterm title bar is neither wrapped nor abbreviated, something which could be done by incorporating one of the other answers here into the above function.)

0

As an alternative, in my .zshrc I abbreviate to the first letter of each directory if the pixel width is over a certain width:

user@machinename:/home/mydirectory1/second_directory
user@machinename:/home/mydirectory1/second_directory/my_actual_directory

becomes:

user@machinename:/h/mydirectory1/second_directory
user@machinename:/h/m/s/my_actual_directory

Here's the zsh function to do so:

     # get the path
     t=`print -P "%m:%~"`;
     t=`echo $t | sed -r 's/([^:])[^:]*([0-9][0-9]):|([^:])[^:]*([^:]):/\1\2\3\4:/'`;
     oldlen=-1;

     # create 4 buckets of letters by their widths
     t1="${t//[^ijlIFT]}";
     t2="${t//[ijlIFTGoQMmWABEKPSVXYCDHNRUw]}";
     t3="${t//[^ABEKPSVXYCDHNRUw]}";
     t4="${t//[^GoQMmW]}";

     # keep abbreviating parent directories in the path until under 456 pixels
     while (( ( ( ${#t1} * 150 ) + ( ${#t2} * 178 ) + ( ${#t3} * 190 ) + ( ${#t4} * 201 ) ) > 4560 && ${#t}!=oldlen)) {
       oldlen=${#t};
       t=`echo $t | sed 's/\/\(.\)[^\/][^\/]*\//\/\1\//'`;
       t1="${t//[^ijlIFT]}";
       t2="${t//[ijlIFTGoQMmWABEKPSVXYCDHNRUw]}";
       t3="${t//[^ABEKPSVXYCDHNRUw]}";
       t4="${t//[^GoQMmW]}";
     };

     PS1=$t

I actually use this to update the terminal title so that with multiple tabs, I can keep straight which tab is which. The full .zshrc to do this is here.

This is very handy as it keeps context and in zsh allows you to quickly tab complete a directory in the same format. (eg typing cd /h/m/s/<tab> will autocomplete to cd /home/mydirectory1/second_directory)

  • How does this apply to the question the OP posed, on how to define the PS1 prompt? – Anthon Feb 28 '16 at 19:38
  • Edited for clarity, $t becomes PS1 – Richard Feb 29 '16 at 2:10
0

Try using this Python script. It cuts off individual sections of the path name, exactly like how you wanted in your question. It also uses the unicode ellipsis that takes up only one column instead of three.

Example output for your path (when given a limit of 30 chars):

/home/mydir…/second…/my_actua…

It's worth noting that this solution correctly handles Unicode in directory names by using wcswidth. ${#PWD}, which other answers have used, will badly misjudge the visual width of any path the contains UTF-8 characters.

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.