I am trying to make the zsh prompt reload a function everytime a new prompt loads. The function outputs a version of pwd but shorter, if the output of pwd was ~/Downloads/Folder the function would output ~/D/Folder. The function works but does not reload if I change directories. This is an issue with zsh and not with the function because the function works fine in ksh and csh. I do not use oh-my-zsh. The function is _collapsed_pwd and the file is $SHELLDIR/cpwdrc.

Here is my .zshrc file

export ZSH="$SHELLDIR/zsh"
export HISTFILE="$XDG_DATA_HOME"/zsh/history

autoload -U +X compinit && compinit
fpath=($ZSH/plugins/zsh-completions/src $fpath)
compinit -d $XDG_CACHE_HOME/zsh/zcompdump-artix-5.8

source $SHELLDIR/aliasrc
source $SHELLDIR/colorsrc # contians color substituions

source $SHELLDIR/cpwdrc # contains _collapsed_pwd

source $ZSH/plugins/fzf-tab/fzf-tab.zsh
source $ZSH/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh

setopt prompt_subst

PS1=$(printf "${BOLD}${BLUE}%s${NORM}@%s:[${BLUE}%s${NORM}]:$ " $USER $(hostname) $(_collapsed_pwd) )

Here is the code for the function _collapsed_pwd

## Collapsed Directory
_collapsed_pwd() {
  pwd | perl -pe '
   BEGIN {
      binmode STDIN,  ":encoding(UTF-8)";
      binmode STDOUT, ":encoding(UTF-8)";
   }; s|^$ENV{HOME}|~|g; s|/([^/.])[^/]*(?=/)|/$1|g; s|/\.([^/])[^/]*(?=/)|/.$1|g

1 Answer 1


You set the content of the prompt once and for all when .zshrc is processed. There is nothing in your code that says to change the content of the prompt when the current directory changes.

One solution is to put the code to change the prompt in a chpwd hook. Remove setopt prompt_subst since you won't be doing any evaluation of the content of PS1.

function set_prompt {
  PS1=$(printf "${BOLD}${BLUE}%s${NORM}@%s:[${BLUE}%s${NORM}]:$ " $USER $(hostname) "${$(_collapsed_pwd)//\%/%%}" )
cd .


  • cd . triggers the chpwd hook once when zsh starts so as to set the prompt initially.
  • The double quotes around the command substitution prevents it from being split into separate word if the output contains whitespace.
  • The ${…//\%/%%} substitution around the output of _collapsed_pwd changes % to %% because % would be interpreted as a prompt escape¹.

Alternatively, do set the prompt_subst option and set PS1 to a string that contains code which will be evaluated each time the prompt is displayed.

setopt prompt_subst
PS1='$(printf "${BOLD}${BLUE}%s${NORM}@%s:[${BLUE}%s${NORM}]:$ " $USER $(hostname) "${$(_collapsed_pwd)//\%/%%}" )'

You can simplify this a lot by using zsh's built-in features to include variable data in the prompt. To start with, here's a prompt that displays the last two components of the current directory:

unsetopt prompt_subst
PS1='%B%F{blue}%n%f%b@%m:[%F{blue}%2~%f]:%(!.#.$) '

To abbreviate directory components, I think you have to run some zsh code, either through a chpwd hook or through prompt_subst. To avoid complexity related to expansion, use a chpwd hook to set psvar and %v to refer to psvar in the prompt string.

unsetopt prompt_subst
PS1='%B%F{blue}%n%f%b@%m:[%F{blue}%1v%f]:%(!.#.$) '
function abbreviate_pwd {
  while [[ $psvar[1] =~ /[^/][^/]+/ ]]; do
cd .

¹ For extra robustness, $USER (see also $USERNAME automatically set by zsh) and $(hostname) (see also $HOST automatically set by zsh) should also be protected, but they normally don't contain any of the problematic characters, assuming that you don't change the value of IFS to include a character that appears in the host name.

  • When I ran the first and second answers I got an error with the flags so I ran the second with "$(_collapsed_pwd)" instead of "${(_collapsed_pwd)//\%/%%}" and it works. The last answer also works but I wanted to use the code I had because I use both ksh and zsh and the last answer does not work in ksh.
    – John smith
    Commented Jun 15, 2020 at 23:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .