What's the ZSH equivalent of BASH's $PROMPT_COMMAND?

25

8

BASH supports a $PROMPT_COMMAND environment variable that defines a command to be executed before any first-level interactive prompt. I'm looking for a ZSH equilvalent of that.

The documentation says that there's a function precmd I can define to achive that; however, I have no idea how to define it from an environment variable.

I've considered passing an environment variable that would make ZSH read a file containing the definition of that function, but ZSH doesn't seem to support such things: it only reads global files and then per-user files. I can replace them but I cannot add to them without modifying the files, which I cannot do.

So how do I define a pre-prompt hook in ZSH via an environment variable, like I'd do using $PROMPT_COMMAND in BASH?

Shnatsel

Posted 2014-03-31T02:13:22.297

Reputation: 352

Truth to be told, I need a post-interactive-command-execution hook, but neither shell provides one so I have to resort to pre-prompt hooks - they seem to be as close as I can get. – Shnatsel – 2014-03-31T02:13:42.207

1Hm, I am wondering, what the difference between post-interactive-command-execution and pre-prompt is. Apart from a conceptual difference, where do you observe actually a difference. (Let's omit the commands exit and exec, ok ;) ) – mpy – 2014-03-31T17:21:54.110

@mpy there's a difference when running a background job, because background jobs are independent from the prompt sequence. – Shnatsel – 2014-03-31T19:52:00.603

1Ok, I got that point. So, how about something like that: start() { eval "$@"; echo post-command-code } and then use a zle-binding to execute the command line with start prepended? – mpy – 2014-03-31T20:14:49.853

@mpy Actually there might be an even better way, here's what I found in shell options: "DEBUG_BEFORE_CMD Run the DEBUG trap before each command; otherwise it is run after each command." Looks like this is exactly what I need! – Shnatsel – 2014-04-02T23:50:48.260

1The DEBUG trap is a nice find, but you still have the problem how to define it. I've extended my answer once more, but I leave it to you to write your own answer regarding the DEBUG trap solution. :) – mpy – 2014-04-03T17:23:34.413

Answers

25

The simplest approach to emulate bash's $PROMPT_COMMAND which comes to my mind is to use the precmd hook, as you already figured out. Define it as

precmd() { eval "$PROMPT_COMMAND" }

and you can do something like that:

$ PROMPT_COMMAND='echo Hello, it is now $(date)'
Hello, it is now Mon, Mar 31, 2014 7:08:00 PM
$ whoami      
user
Hello, it is now Mon, Mar 31, 2014 7:08:21 PM     
$

Please note the single quotes in that example, otherwise $(date) will get expanded too early, i.e. already when defining $PROMPT_COMMAND and not when called before the prompt.


If you want to preserve (and don't want to alter) the existing definition, you can use that approach:

$ prmptcmd() { eval "$PROMPT_COMMAND" }
$ precmd_functions=(prmptcmd)

With that the prmptcmd functions is executed after the existing precmd() function.


Finally, here is a way which is suitable for use in a program package, which neither should modify user or system files nor can enter the commands interactive.

An example to spawn a bash session could be

PROMPT_COMMAND="echo foo" bash

To spawn zsh you can use

ZDOTDIR=/program/dir zsh

which causes /program/dir/.zshrc to be sourced. In this file the precmd() hook can be defined as explained above. If you want the user's settings in addition include source $HOME/.zshrc etc. in the program's .zshrc, too. This setup is maintainable, as no files outside the program directory are modified.


As a last addition, here is a proof of concept how to keep the newuser welcome, too. Use the following code in your /program/dir/.zshenv rc config file:

echo define precmd, traps, etc.

autoload -Uz zsh-newuser-install

if [[ ! -e "$HOME/.zshrc" ]]; then
  zsh-newuser-install -f
  mv $ZDOTDIR/.zshrc $HOME/.zshrc
else
  builtin source $HOME/.zshrc
fi

mpy

Posted 2014-03-31T02:13:22.297

Reputation: 20 866

That much I figured. The problem is - how do I define the precmd hook via an environment variable? Is there a mechanism for adding hooks or code without modifying files? Or how do I do it at least without writing to the global and user-global ".zprofile" and similar files? Like, can I add my own .zprofile that won't replace existing ones? – Shnatsel – 2014-03-31T19:56:45.077

1Also your use of precmd hook here would replace any alread existing precmd hooks; zsh docs mention I can make an array of functions that will coexist but I have no idea how to do that. – Shnatsel – 2014-03-31T19:58:01.433

1(1) What do you mean with how do I define the precmd hook via an environment variable? The example I presented works IMHO like bash mechanism. (2) You can add the hook via command-line, but then it's not permanent. What's the problem with the modification of your .zshrc? (3) An example: foo() { echo foo }; bar() { echo bar }; precmd_functions=(foo bar) This executes foo() and bar() in addition to precmd(). – mpy – 2014-03-31T20:20:27.107

It's supposed to be used internally from an application that spawns the shell. I'd very much prefer avoiding modifying the user's .zshrc because I don't want any side effects on shells spawned from regular terminal. Also modifying .zshrc makes installation and clean removal of the program really tricky. Can I define my own precmd hook without modifying the system- or user-wide files? – Shnatsel – 2014-04-01T20:45:21.370

2Ok, that clarifies a lot -- a minimal example for bash would then be PROMPT_COMMAND="echo foo" bash, right? Is this a possibility for spawning zsh: ZDOTDIR=/program/dir zsh. Then /program/dir/.zshrc is sourced upon start where you can define the precmd() hook. If you want the user's in addition include source $HOME/.zshrc etc. in the program's zshrc. This should be easy to maintain, as no files outside the program dir are modified. – mpy – 2014-04-01T21:27:14.990

1@Shnatsel : I extended my answer. Perhaps you can also edit your question to include the additional info from your comments. – mpy – 2014-04-02T18:40:31.247

Uh, turns out that still isn't sufficient because zsh has a welcome function what writes out .zshrc - which will not be triggered if I override ZDOTDIR. And I don't want to break the welcome because without it the shell is really barebones and not what the user expects. Maybe there's a way to trigger it manually if the file does not exist or something along those lines? – Shnatsel – 2014-04-02T23:46:31.133

5

As @mypy states, Zsh's precmd works similarly to Bash's PROMPT_COMMAND.

Here's an example that works for Bash or Zsh and doesn't use eval:

## ~/myprompt.sh

# 'ZSH_VERSION' only defined in Zsh
# 'precmd' is a special function name known to Zsh

[ ${ZSH_VERSION} ] && precmd() { myprompt; }

# 'BASH_VERSION' only defined in Bash
# 'PROMPT_COMMAND' is a special environment variable name known to Bash

[ ${BASH_VERSION} ] && PROMPT_COMMAND=myprompt

# function called every time shell is about to draw prompt
myprompt() {
  if [ ${ZSH_VERSION} ]; then
    # Zsh prompt expansion syntax
    PS1='%{%F{red}%}%n%{%f%}@%{%F{red}%}%m %{%F{cyan}%}%~ %{%F{white}%}%# %{%f%}'
  elif [ ${BASH_VERSION} ]; then
    # Bash prompt expansion syntax
    PS1='\[\e[31m\]\u\[\e[0m\]@\[\e[31m\]\h \[\e[36m\]\w \[\e[37m\]\$ \[\e[0m\]'
  fi
}

Run from shell init scripts:

## ~/.bashrc
. ~/myprompt.sh

and:

## ~/.zshrc
. ~/myprompt.sh

The prompts here are just examples. One can definitely do much trickier stuff.

For details of setting prompt functions, see: http://zsh.sourceforge.net/Doc/Release/Functions.html#index-precmd and http://www.gnu.org/software/bash/manual/bashref.html#Printing-a-Prompt.

For details of prompt expansions, see http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html and http://www.gnu.org/software/bash/manual/bashref.html#Printing-a-Prompt.

jwfearn

Posted 2014-03-31T02:13:22.297

Reputation: 151