Multiline shebang with zsh and running pyenv under cron
3 minutes read | 500 words by Ruben BerenguelThese probably qualify as the most one weird trick I have figured out this year.
Multiline shebang
Basically this is an answer to how can I configure configure the Python environment in the shebang? Like, say, to start a virtual environment. You can find many examples with bash here. But, how do you use it with zsh? And what it is?
It is text after a shebang expression in a script that is run by zsh, while eventually running the rest of the code. It feels a bit like a quine.
I faced this when writing my own provider script for Tip (a very recommended small app for Mac). Although it may not be required, but was fun as a problem to crack. The issue was twofold: I needed to run it under zsh
and I wanted to run my script under a virtual environment.
#!/usr/local/bin/zsh
"""true"
source /Users/ruben/.pyenvzshrc
export PYENV_VIRTUALENV_DISABLE_PROMPT=1
pyenv activate tip-provider
exec python "$0" "$@"
exit 127
"""
__doc__ = "Script for https://github.com/tanin47/tip"
[…python code in here…]
What is this? First, this is going to be executed as a zsh
script. So, we have
ZSH sees
- an empty string,
- a string containing true, which evaluates via expansion to
true
, inside quotes, so, another empty string. - Now some normal shell commands, load some scripts to prepare an environment. This
.pyenvzshrc
is a reduced.zsh
without any configuration and just there sopyenv
works as expected. - Disable the painful notification from
pyenv
- Runs python (the python from the correct environment), on this script itself with the same arguments
- Exit! This is important! This way nothing else is seen by
zsh
. It has to exit in error to prevent the shell seeing the rest.
Python sees
- A commented line (shebang)
- A triple-quoted string, which is then automatically added as the module docstring
- Modification of the docstring for consistency
- The rest
Neat, isn’t it? (not)
Running pyenv
in cron
This is a repeated issue I have faced: running things under cron fails for… shell reasons. I wanted to run bear-note-graph regularly, to always have my note graph fresh on iCloud. The goal is similar to the previous: running Python in a specific virtual environment.
Luckily, this is easier (although a bit slower):
#!/usr/local/bin/zsh
source /Users/ruben/.zshrc
pyenv activate bear-note-graph || true >/tmp/bear_graph_tmp_stdout.log 2>/tmp/bear_graph_tmp_stderr.log
bear-note-graph >>/tmp/bear_graph_tmp_stdout.log 2>>/tmp/bear_graph_tmp_stderr.log
mv /tmp/bear_graph* /Users/ruben/Library/Mobile\ Documents/com\~apple\~CloudDocs/
- Screw it and just evaluate the
.zshrc
file! (this could be changed with the modified one in the previous script) - Activate the virtual environment, and redirect any errors to create output files
- Run
bear-note-graph
with default settings (I wrote this, so the defaults are the setup I like) - Copy the results from
/tmp/
to iCloud’s root
Conclusion
Well, not much. These are interesting tricks that I want to remember, hence why I wrote this post.
Note that the first one is specific for zsh
, other shells behave slightly differently. For the second, likewise, the main issue is how to start pyenv
in a way that works.