Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to truncate working directory in prompt to show first and last folder?

Tags:

bash

macos

prompt

I'm looking for a way to implement a responsive working directory where I at least want to display the first and last folder. It should be based on the available with in the terminal. E.g. something like $(exprtput cols- 35)}.

Imagine the current working directory is ~/workspace/i/keep/my/projects/project1/src

Then I would like to display the prompt like:

~/workspace/../src

If the terminal is big enough I would like to have:

~/workspace/../project1/src

or

~/workspace/../projects/project1/src

~/workspace/../my/projects/project1/src

and so forward.

If there is enough place it should even display the full path. To win space the home dir should always be displayed like ~.

Is this possible using a pure bash script on OSX?

like image 926
Marcel Overdijk Avatar asked Oct 24 '14 19:10

Marcel Overdijk


People also ask

How do I change my working directory to the previous one?

If we type cd followed by nothing, cd will change the working directory to our home directory. A related shortcut is to type cd ~user_name . In this case, cd will change the working directory to the home directory of the specified user. Typing cd - changes the working directory to the previous one.

How can I change my bash prompt to show my working directory?

In value of PS1 , \w or \W can be used to include working directory in the prompt. Change the value of PS1 in your $HOME/. bashrc file to change it for every terminal.

How do I show the directory path in UNIX prompt?

By default, bash shows just your current directory, not the entire path. To determine the exact location of your current directory within the file system, go to a shell prompt and type the command pwd.


1 Answers

Solution for the Simplest Case

I would like to display the prompt like: ~/workspace/../src

This will display that style of path while ending the prompt with the usual $ and space:

PS1='$(pwd | sed -E -e "s|^$HOME|~|" -e '\''s|^([^/]*/[^/]*/).*(/[^/]*)|\1..\2|'\'') \$ '

Although my intention was that this work on OSX's BSD sed, I have only tested it on Linux with GNU sed.

This version provides only the one output format. It does not change format as the terminal gets wider.

How it works

The key element in the definition of the new PS1 is the command substitution. That command is:

pwd | sed -E -e "s|^$HOME|~|" -e 's|^([^/]*/[^/]*/).*(/[^/]*)|\1..\2|'
  • pwd

    This prints the current working directory to stdout

  • "s|^$HOME|~|"

    If the path begins with $HOME, replace it with ~. Because $HOME is a shell variable, this command has to be in double-quotes so that the shell does variable substitution on $HOME.

  • 's|^([^/]*/[^/]*/).*(/[^/]*)|\1..\2|'

    The regex consists of three parts:

    • ^([^/]*/[^/]*/) matches the first two directories and saves them in \1.

    • .* this matches whatever follows the first two directories up until the last directory

    • (/[^/]*) matches the last subdirectory and saves it in \2.

    If there are enough directories for the regex to match, then the whole path is replaced with \1..\2.

More Complex Solution

Suppose that we want the format of the prompt to change with the available width of the terminal, showing more of the final directories if there is space to do so. Thus, in a narrow terminal, /home/user/dir1/dir2/dir3/dir4 might be displayed as ~/dir1/../dir4 but in a wider terminal it would appear as ~/dir1/../dir3/dir4 and in a still wider terminal, it would appear as ~/dir1/dir2/dir3/dir4.

In that case:

PS1='$(pwd|awk -F/ -v "n=$(tput cols)" -v "h=^$HOME" '\''{sub(h,"~");n=0.3*n;b=$1"/"$2} length($0)<=n || NF==3 {print;next;} NF>3{b=b"/../"; e=$NF; n-=length(b $NF); for (i=NF-1;i>3 && n>length(e)+1;i--) e=$i"/"e;} {print b e;}'\'') \$ '

Because this solution requires more calculations, awk is used.

This code allows the user to adjust how much of the width of the terminal should be taken up by the directory list. This is done by adjusting the constant in the code n=0.3*n. As written, this restricts the directory display, if possible, to only 30% of the terminal width.

How it works

The key element of the code is this command:

pwd | awk -F/ -v "n=$(tput cols)" -v "h=^$HOME" '{sub(h,"~");n=0.3*n;b=$1"/"$2} length($0)<=n || NF==3 {print;next;} NF>3{b=b"/../"; e=$NF; n-=length(b $NF); for (i=NF-1;i>3 && n>length(e)+1;i--) e=$i"/"e;} {print b e;}'

The code considers these cases:

  1. The directory string is short enough already. In other words, its length is fits in the allotted space. In this case, it is displayed as is.

  2. There are only three directories. For example, ~/dir1/dir2. Our format does not allow this to be shortened. Thus, for this case, directory string is displayed as is.

  3. There are four or more directories and the directory string must be shortened to fit in the space. In this case, the directory string is divided into the beginning, which is stored in the variable b, and the ending, which is stored in the variable e. The beginning contains two directories and the dot-dot string, such as ~/dir1/../. Starting from the last directory, directories are added to the end string e as space allows.

More details on the awk command

  • -F/

    This sets the field separator to /. As a consequence, each directory will appear as a separate field.

  • -v "n=$(tput cols)" -v "h=^$HOME"

    This creates two variables that we will need. n has the width of the terminal in columns. h has a regex matching the the user's home directory.

  • sub(h,"~")

    If the path starts with the user's home directory, this replaces it with a ~.

  • n=0.3*n

    This sets a goal for the desired width of the directory string in the prompt. I like the directory string to be not too long, so n=0.3*n sets the goal to 30% of the width of the terminal in columns. As discussed elsewhere, this can be replaced with another formula as per your personal preferences.

  • b=$1"/"$2

    b is the variable that contains the beginning of the directory string. Here, we set it to the first two directories. If the path, for example, was ~/dir1/dir2/dir3, then this sets b to ~/dir1.

  • length($0)<=n || NF==3 {print;next;}

    If the complete directory string is no longer than our goal, n, or else if there are only three directories in the directory string, then print out the directory string as is and then quit.

  • NF>3{b=b"/../"; e=$NF; n-=length(b $NF); for (i=NF-1;i>3 && n>length(e)+1;i--) e=$i"/"e;}

    If we get, then our directory string needs to be shortened. So, we add the abbreviation string /../ to the end of b. For the first trial, we set the end string, e, to be the last directory. (In awk, NF is the number of fields. So, $NF is the last field (directory) on the line.) We then subtract from n the length of b and e. The value of n that remains is the amount of space that we have left. Then starting with the second to last directory on the line, we try to add one directory at a time to e without going over our goal for how long the directory string should be.

  • {print b e;}

    This prints the final directory string that is used in the prompt.

Full Width Display

To change the amount of space taken up by the directory display, we change the command that read n=0.3*n (which aimed for 30% width) to n=1*n (which aims for full width even if the $ prompt overflows to the next line):

PS1='$(pwd|awk -F/ -v "n=$(tput cols)" -v "h=^$HOME" '\''{sub(h,"~");n=1*n;b=$1"/"$2} length($0)<=n || NF==3 {print;next;} NF>3{b=b"/../"; e=$NF; n-=length(b $NF); for (i=NF-1;i>3 && n>length(e)+1;i--) e=$i"/"e;} {print b e;}'\'') \$ '

Depending on your preferences, you may want other formulas. For example, try n=n-10 and it will try to leave some (but not many) available spaces for you at the end of the prompt.

PM2Ring suggested that the $ prompt always be placed on the next line, to do that, we place a \n (newline) before the \$ prompt:

PS1='$(pwd|awk -F/ -v "n=$(tput cols)" -v "h=^$HOME" '\''{sub(h,"~");n=1*n;b=$1"/"$2} length($0)<=n || NF==3 {print;next;} NF>3{b=b"/../"; e=$NF; n-=length(b $NF); for (i=NF-1;i>3 && n>length(e)+1;i--) e=$i"/"e;} {print b e;}'\'') \n\$ '
like image 68
John1024 Avatar answered Oct 12 '22 13:10

John1024