I just decided to try zsh (through oh-my-zsh), and am now playing with precmd to emulate a two-line prompt that has right prompts in more than just the last line.

So I clone the default theme, and inspired by this post (that I'm using to learn a lot too), i do somehting like this (I'll add colors later):

function precmd {
    local cwd="${(%):-[%~]}"
    local who_where="${(%):-%n@%m}"
    local git_info=${(%)$(git_prompt_info)}
    local right_prompt="     $git_info [$who_where]"
    local left_prompt="${(r:(($COLUMNS - ${#${right_prompt}})):: :)cwd}"

    echo "$left_prompt$right_prompt"

And it works. But I can't help but wonder: is zsh defining all those variables every time precmd is called?

I've been googling for closures, scope and namespacing in relation to zsh, looking to attach the local vars as data to precmd, so it doesn't need to redefine the variables every time, but I have found nothing. Is there some way to do what I'm trying, or should I just drop it?

As a side note, and only if it is related, what does "to have a function loaded" mean?


Zsh doesn't have anything like closures or packages or namespaces. Zsh lacks a bunch of things required to have true closures:

  • Functions aren't first class. You can't pass functions around as arguments to other functions, and functions can't return other functions. (You can pass the name of a function to call, but that's not the same as passing the function itself).

  • You can't have nested functions. All functions in zsh are global. You must prefix your function names to avoid conflicts. Note especially that functions will shadow external programs with the same name. If you have a function called ls, it will be called instead of the program ls. This can be useful, except if you do it by accident.

  • Variables are dynamically scoped, not statically scoped like in most modern languages. Even if you could have nested functions, inner functions wouldn't close over the local variables of outer functions in the way you would normally expect. You couldn't use them to make modules the way people do in, say, Javascript.

  • Zsh does have anonymous functions, but without any of these other things they're not useful for much.

So basically, the best you can do is to prefix all your functions and global variables.

I'll also point out that you should do define your precmd like this:

% autoload -Uz add-zsh-hook
% add-zsh-hook precmd my_precmd_function

add-zsh-hook lets you hook your function into precmd without it overwriting any other functions that might also want to hook precmd.

What it means to have a function loaded is a separate question. Zsh has an autoloading feature that only loads functions from disk when they're actually called. When you do autoload -Uz foobar, that makes the function named foobar available to call. When you actually call foobar, that loads the definition from disk.

  • 1
    This is what I was looking for. I can't up-vote yet, but kudos. – ferhtgoldaraz Oct 8 '13 at 20:04
  • @Matt, extra credit for mentioning the add-zsh-hook. – SuperMagic Oct 8 '13 at 20:09
  • For point 1, you can pass the definition of a function as argument : myfunc $functions[otherfunction] which myfunc can call for instance as () {eval $1} args (not that I would recommend doing that that). – Stéphane Chazelas Sep 24 '18 at 15:01
  • For point 3, you can have static scope using private variables (in the zsh/param/private module). – Stéphane Chazelas Sep 24 '18 at 15:01
  • I had just this crazy idea and it seems not only to work with bash but also zsh: gist.github.com/pinkeen/287ad64d951f7bf138afce975d3bce7b – pinkeen Jun 6 at 18:26

No, closures are too sophisticated for zsh. Zsh is designed to interpret small scripts that are not far removed from direct interaction. It doesn't have fancy language features that are very useful for programming in the large but less so for the kind of small tasks that shells are typically used for.

Note that if there was some form of closure that allowed the value of the variables to be pre-computed once and for all and then stored, the values would not be updated when something changes that causes the information to become invalid.

$git_info and the derived variables can change at any time due to a modification to a file checked into git or to the git repository. So they need to be recomputed each time anyway.

You could cache the values of cwd and who_where in a global variable, since they don't change under normal operation. cwd changes when the currrent directory changes so it would need to be updated from chpwd. However, these variables are extremely quick to compute, so there is no point in bothering. The expensive computation here is running git_prompt_info, and that can change at any time.

When you're displaying information between each command, it may be a better idea to put it as part of the prompt (PS1 or the psvar array). Zsh knows that it must redisplay the prompt in a variety of circumstances, whereas it knows nothing about what you print from precmd.

  • +1 for addressing the need to recompute the values each time. – chepner Oct 17 '13 at 16:49

Yes, those variables are (re)defined every time you call the function.

If you want to initialize them only once, you could simply move them to the top-level, out of the function.

  • Thanks for the answer. Though I am trying to find alternatives to using the global scope so I can battle it's pollution in the future if the need arises. Maybe there is some alternative to this? – ferhtgoldaraz Oct 8 '13 at 17:14
  • 1
    Not really. You can use a prefix on your variables, say LRPROMPT_, to limit possible name collisions/pollution.... but it's still possible. Also, it's just your shell and prompt. Collisions can happen, but it's not that big a deal unless you're doing quite a bit more in the shell than is probably reasonable. I use oh-my-zsh AND have more than 2000 lines in custom zsh configuration (beyond omz) and collisions really aren't a problem. I do use prefixes, though. – SuperMagic Oct 8 '13 at 17:20
  • If your shell startup files are so big that name collisions are really a problem, you are almost certainly going to have other, more serious issues as well :) – Ben Oct 8 '13 at 17:30
  • @Ben, that number includes functions which have considerable white space and local variables. If I had 2000 global aliases and variables.... that would be a problem. – SuperMagic Oct 8 '13 at 17:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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