I wanted a $PWD shrinking for my $PROMPT with the following requirements:

  • $HOME is replaced by ~
  • All subpaths except the last one are replaced by their initial letters
  • Reasonably efficient

This is my attempt, with tests:


function shrinkpath()
  local dir="${1/#$HOME/~}"
  if [ $dir = "~" ]; then
    echo "~"
  local split=(${(s:/:)dir%/*})
  local prefix="/"
  if [ "$split[1]" = "~" ]; then
  local mid=$(for i ("$split[@]") ; do echo -n "${i:0:1}/" ; done)
  local last="${dir##*/}"
  echo "$prefix$mid$last"

echo "$(shrinkpath $HOME)"
echo "$(shrinkpath $HOME/foo)"
echo "$(shrinkpath $HOME/foo/bar/baz)"
echo "$(shrinkpath /)"
echo "$(shrinkpath /foo)"
echo "$(shrinkpath /foo/bar/baz)"



Seems to be doing the trick but I feel it could be smaller/simpler. In particular, my intuition tells me that I should be able to get away with a single check for "~" instead of two, and that I might be using too many intermediate variables. I am also not sure about whether there is a better way to do the "split by / and get the first letter of each subpath" part.


1 Answer 1


As this is for a prompt:

shrinkpath () {
  local split dir=${PWD/#~\//\~/}
  case $dir in
    ~) dir='~' ;;
    *) split=( "${(@s:/:)dir}" )
       dir=${(j:/:M)split#?}${split[-1]:1} ;;

precmd_functions=( shrinkpath )
PS1='%1v%# '

This is similar to your current approach (split->process->join). The # modifier combined with the (M) flag is used for removing everything except the first character in every path component (after HOME prefix replacement). The last component is appended with its first character removed.

A prefix variable is not needed as the s flag is used inside double quotes with the @ flag (man zshexpn says of s:string: "For historical reasons, the usual behaviour that empty array elements are retained inside double quotes is disabled for arrays generated by splitting" ... "To override this behaviour, supply the (@) flag as well").

psvar is an array to use with the %v sequence in the prompt string. For testing, I added print -r -- $dir to the end of shrinkpath() so that different PWDs can be passed to see what it does with them (note the fourth test where HOME appears as a prefix, but isn't the user's home):

% PWD=$HOME shrinkpath
% PWD=$HOME/foo shrinkpath
% PWD=$HOME/foo/bar/baz shrinkpath
% PWD=${HOME}foo/bar/baz shrinkpath
% PWD=/ shrinkpath
% PWD=/foo shrinkpath
% PWD=/foo/bar/baz shrinkpath

Using %v in the prompt string and making use of a function to alter the psvar array should be more efficient than using the PROMPT_SUBST option with $(shrinkpath) as that approach would fork a subshell.

The * case could be changed to:

       dir="${(@j:/:M)${(@s:/:)dir}#?}${dir##*/(?|)}" ;;

*/*/* to skip the split/join process when it's not needed. Zsh can nest parameter expansions like ${${${var}}}, so the intermediate split array is not really needed.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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