Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?

share|improve this question
1  
Fortunately I remember where I've read how to customize shell prompt: tldp.org/HOWTO/Bash-Prompt-HOWTO/x783.html Thanks go to Giles Orr, author of Bash Prompt HOWTO and people who contributed to it. – stemd Feb 28 at 11:15
    
See also unix.stackexchange.com/a/216871/117549 (ksh-based, but similar idea) – Jeff Schaller Feb 28 at 12:26
up vote 13 down vote accepted

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 $ 
share|improve this answer

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

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`'
share|improve this answer
    
More on color at unix.stackexchange.com/q/124407/10043 – Michael Durrant Feb 28 at 16:53

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$
share|improve this answer

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:~
$ 
share|improve this answer

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.)

share|improve this answer

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)

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

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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