13

I have a function in my ~/.zshrc:

findPort() {
    lsof -t -i :$1
}

The usual invocation is findPort 3306.

I want to run it with elevated privileges. But I get "command not found".

➜  git 🍔 sudo findPort 3306
sudo: findPort: command not found

I presume the reason is that the root user either runs as a non-interactive shell (thus does not refer to a .zshrc), or refers to a different .zshrc.

I have seen similar questions regarding alias, but no question regarding user-defined functions. The answers for this problem regarding alias involves adding an alias to ~/.zshrc:

alias sudo='nocorrect sudo '

Or perhaps:

alias sudo='sudo '

I have tried both of these solutions, and the problem still exists (yes I've relaunched the shell).

I have also tried running sudo chsh to ensure that my root shell runs under zsh. None of these solutions removes the "command not found" problem.

Is there a way to run my user-defined functions under sudo?

1

4 Answers 4

16

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
4
  • I need a solution that's as short as typing sudo. I was able to adapt your anonymous function solution to do just that. I have added the following function to my ~/.zshrc, which is a function to run other functions with elevated privileges: sdo() { sudo zsh -c "(){$functions[$1]} ${@:2}" }
    – Birchlabs
    Oct 20, 2016 at 16:04
  • 1
    @Birchlabs, see edit Oct 20, 2016 at 16:28
  • Thanks; I've updated my answer to use the newer shortcut you've proposed.
    – Birchlabs
    Oct 20, 2016 at 16:38
  • Adding the function definition to the script executed through sudo is smart. But not smart enough if that function use another function (autoloaded or not). Nov 15, 2018 at 23:25
4

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.

2
  • This does indeed work. Though it's not quite the workflow I had in mind. My desire is to remain in a prompt as my usual user, and elevate just my user-defined function myFunc, by typing sudo myFunc — the same way I would elevate any process (like sudo rm -rf .). This solution elevates my entire prompt and all future functions, as well as changing my user for all future functions — which is a bit overkill.
    – Birchlabs
    Oct 24, 2016 at 11:22
  • The sudo -s command will launch another instance of your shell as the superuser. If you execute that command interactively, your new shell will be interactive. But the command sudo -s findPort 3306 will run the command findPort 3306 in your shell as the superuser then exit with the status code of that command. Nov 15, 2018 at 23:33
2

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
1

This seems to work for me:

function sudo (){
  args="$@"
  /run/wrappers/bin/sudo -u "$USER" zsh -i -c "$args"
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .