"command not found" when sudo'ing function from ~/.zshrc

sudo runs commands directly, not via a shell, and even if it ran a shell to run that command, it would be a new shell invocation, and not one that reads your ~/.zshrc (even if it started an interactive shell, it would probably read root's ~/.zshrc, not yours unless you've configured sudo to not reset the $HOME variable).

Here, you'd need to tell sudo to start a new zsh shell, and tell that zsh to read your ~/.zshrc before running that function:

sudo zsh -c '. $0; "$@"' ~/.zshrc findPort 3306

Or:

sudo zsh -c '. $0; findPort 3306' ~/.zshrc

Or to share your current zsh functions with the new zsh invoked by sudo:

sudo zsh -c "$(functions); findPort 3306"

Though you might get an arg list too long error if you have a lot of functions defined (like when using the completion system). So you may want to limit it to the findPort function (and every other function it relies on if any):

sudo zsh -c "$(functions findPort); findPort 3306"

You could also do:

sudo zsh -c "(){$functions[findPort]} 3306"

To embed the code of the findPort function in an anonymous function to which you pass the 3306 argument. Or even:

sudo zsh -c "$functions[findPort]" findPort 3306

(the inline script passed to -c is the body of the function).

You could use a helper function like:

zsudo() sudo zsh -c "$functions[$1]" "$@"

Do not use:

sdo() { sudo zsh -c "(){$functions[$1]} ${@:2}" }

As the arguments of sdo would undergo another level of shell parsing. Compare:

$ e() echo "$@"
$ sdo e 'tname;uname'
tname
Linux
$ zsudo e 'tname;uname'
tname;uname

A very simple solution for me was to invoke an interactive shell using sudo -s, which appears to work on my macOS bundled version of zsh (5.2). This provides me with the functions from my ~/.zshrc.

From the sudo manpage, which doesn't really hint at this behaviour:

-s, --shell
Run the shell specified by the SHELL environment variable if it is set or the shell specified by the invoking user's password database entry. If a command is specified, it is passed to the shell for execution via the shell's -c option. If no command is specified, an interactive shell is executed.

I'm not sure what your use case is, but I suspect you're looking for an interactive solution.


I was able to produce a reusable shorthand for one of the solutions described in Stéphane Chazelas' answer. [Edit: Stéphane has iterated on the original shortcut I proposed; the code described here is a newer version, of his making]

Put this into your ~/.zshrc:

sdo() sudo zsh -c "$functions[$1]" "$@"

Now you can use sdo as a "sudo for user-defined functions only".

To confirm that sdo works: you can try it out on a user-defined function that prints your username.

➜ birch@server ~/  test() whoami

➜ birch@server ~/  test
birch

➜ birch@server ~/  sdo test
root

Tags:

Sudo

Zsh