caffeinate
expects to execute a command in a new process.
To interpret a zsh
function, you need a zsh
command.
And you'd need to pass the definition of that function (as well as other functions it may need) to it, for instance with:
mysleep() {
sleep 2
}
caffeinate zsh -c "$(functions mysleep);mysleep"
functions mysleep
dumps the definition of the mysleep
function which we pass to that new zsh
for interpretation before calling the function, so that zsh
invoked by caffeinate
ends up interpreting:
mysleep() {
sleep 2
};mysleep
If we compare to bash
's:
mysleep() {
sleep 2
}
export -f mysleep
caffeinate bash -c "mysleep"
(which is 2 characters shorter to type), bash
will do:
execve("/path/to/caffeinate",
["caffeinate", "bash", "-c", "mysleep"],
["BASH_FUNC_mysleep%%=() { sleep 2\n}", rest-of-environment])
While with zsh
, we get:
execve("/path/to/caffeinate",
["caffeinate", "zsh", "-c", "mysleep () {\n\tsleep 2\n};mysleep"],
[rest-of-environment])
I see several advantages of that latter approach:
- we have full control: we know how we pass the function definition, how it is being used. There's less scope for nasty things like the type of shellshock here.
- as the name of the bash environment variable that carries the function definition contains
%
characters (and even if it didn't, think of sudo
for instance), we're not guaranteed guaranteed that caffeinate
will propagate it to the bash
command it runs.
- if it is propagate, because the function definition is stored in envp[] instead of argv[], that means it pollutes the environment of every other command executed in that environment (including
sleep
for instance in this example).
- (minor) even though the
bash
shell code is shorter, that's more data passed to execve()
so contributes more toward the E2BIG limit of that system call.
If you wanted to use the environment, you could still do:
FUNCS=$(functions mysleep) caffeinate zsh -c '
eval "$FUNCS";mysleep'
In the case of caffeinate
here, that is where we only need for caffeinate
to run while the function is running, not necessarily for it to run the function, we can use other approaches like:
mysleep | caffeinate cat
cat
will run as long as mysleep
runs. mysleep
would still run in a separate process and that affects the stdout of mysleep
though.
mysleep 3> {fd}>(caffeinate cat)
would solve both problems.
As above, that creates a pipe between mysleep
and cat
. But the writing end of the pipe is now on a newly allocate file descriptor above 10 (stored in $fd
) that mysleep
will typically not write to. cat
will therefore read nothing but wait until end-of-file on the pipe which will only happen when mysleep
(and all the children processes that inherit that fd) terminates.
bash
which has function export support. Here, it's forzsh
– Stéphane Chazelas Jun 02 '17 at 06:04/bash
, (and/zsh
), so the boundaries seem a bit fuzzy. – agc Jun 02 '17 at 06:22parallel
borrowed from Tange's 5/17/15 post on unix.com:foo() { echo "$1$1$1$1$1$1" ; } ; export fun="$(typeset -f foo)"; parallel 'eval "$fun";'foo ::: baz
outputsbazbazbazbazbazbaz
, also 2) usingxargs
, adapted from jawsnnn's post from the same URL:seq 10 | xargs -I% --max-procs=5 zsh -c "$(typeset -f foo)"';foo "%"'
– agc Jun 03 '17 at 14:24