Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash prompt shortening

Tags:

I've just moved from tcsh to bash and I particular missed the directory-shortening prompt options using %c02 (with ellipsis also set).

I see that PROMPT_DIRTRIM does almost the right thing (except ellipsis, I think) but I'm only on bash 3 (on OS X). I found this recipe elsewhere on SO, which shortens on total length, so breaks pathnames in the middle of directories, which I didn't like.

So I came up with this:

PROMPT_DIRTRIM=2   ## from bash4, but used here
dirtrim() 
{
    local NAME="$1" start= endelts=
    [[ "$NAME" =~ ^"$HOME"(/|$) ]] && NAME="~${NAME#$HOME}"  ## $HOME ==> ~
    IFS=/ read -ra elts <<< "$NAME";          ## split $PWD on "/"
    start=$((${#elts[@]}-${PROMPT_DIRTRIM}))  ## first element to retain
    if [ ${start} -gt 1 ]; then     
        for ((i=${start}; i<${#elts[@]}; i++)); do 
            endelts="${endelts}/${elts[$i]}"; ## concat together the trailing path
        done
        NAME="...${endelts}"
    fi
    echo "$NAME"    
}
PS1='\h:$(dirtrim "$PWD")\$ '

It works:

blackat:~$ cd ~/Library/Application\ Support/Apple
blackat:.../Application Support/Apple$

But I'm newish to bash and unhappy with the explicit for (()) loop; however, I couldn't seem to find any other way to rejoin the last entries of the split elts array in a way that deals correctly with spaces in dir names (e.g., using ${elts[@]:${start}}). Any hints to do that or other improvements?

(By the way, I think this is a programming question, to the extent that bash is a programming language....)

like image 464
Andrew Jaffe Avatar asked May 27 '16 22:05

Andrew Jaffe


People also ask

How do I shorten a directory path?

Use PUSHD , CD , and POPD to go back and forth to a specific path in order to temporarily use a shorter relative path.

How do I shorten a command line in Ubuntu?

Just enter PS1='\u:\W\$ ' and press enter.

What is the Bash prompt?

The default BASH prompt is the one you see when you first open a terminal or command line. It usually looks something like this: username@hostname:~$ Alternatively, it may look like this: (base) [username@localhost ~]$ The first part of the prompt tells you the user that's currently logged in.


2 Answers

You could try this:

if ((start > 1)); then
  name=$(IFS=/; echo .../"${elts[*]:start}")
  # If your terminal is correctly set up for unicode, you can save two character positions:
  # name=$(IFS=/; echo …/"${elts[*]:start}")
fi

Note that in bash, in an arithmetic context, which includes the inside of ((...)) and array subscripts, you can write just the name of the variable; no need for sigils.

Another way to do it would be

if ((start > 1)); then
  printf -v name "/%s" "${elts[@]:start}"
  name=...$name
fi

Yet another solution, using regex captures in the BASH_REMATCH array rather than splitting and rejoining the string:

dirtrim () { 
  local path="$1";
  [[ $path =~ ^"$HOME"(/.*)? ]] && path=~${BASH_REMATCH[1]};
  ((PROMPT_DIRTRIM)) &&
    [[ $path =~ ...*((/[^/]*){$PROMPT_DIRTRIM}) ]] &&
    path=…${BASH_REMATCH[1]};
  echo "$path"
}

The ((PROMPT_DIRTRIM)) test is not completely robust because of the peculiarities of bash evaluation in an arithmetic context. For distribution you might prefer something like [[ $PROMPT_DIRTRIM =~ ^[1-9][0-9]*$ ]]

like image 123
rici Avatar answered Sep 28 '22 04:09

rici


Not really an answer, but you might want to to look at how mksh accomplishes this:

PS1=${| local e=$? (( e )) && REPLY+="$e|" REPLY+=${USER:=$(ulimit -c 0; id -un 2>/dev/null || echo \?)} REPLY+=@${HOSTNAME%%.*}: local d=${PWD:-?} p=~; [[ $p = ?(*/) ]] || d=${d/#$p/~} local m=${%d} n p=...; (( m > 0 )) || m=${#d} (( m > (n = (COLUMNS/3 < 7 ? 7 : COLUMNS/3)) )) && d=${d:(-n)} || p= REPLY+=$p$d return $e }

Unfortunately, it uses some extensions that are not in bash, I believe.

Aso, since you're switching shells... have you considered fish?

It does this out-of-the-box, and then some.

like image 24
Geoff Nixon Avatar answered Sep 28 '22 02:09

Geoff Nixon