Andrew Jaffe
Andrew Jaffe

Reputation: 27107

Bash prompt shortening

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

Upvotes: 5

Views: 1416

Answers (2)

rici
rici

Reputation: 241909

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]*$ ]]

Upvotes: 3

Geoff Nixon
Geoff Nixon

Reputation: 4994

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.

Upvotes: 1

Related Questions