9

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?

11

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
  • 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 '16 at 16:04
  • 1
    @Birchlabs, see edit – Stéphane Chazelas Oct 20 '16 at 16:28
  • Thanks; I've updated my answer to use the newer shortcut you've proposed. – Birchlabs Oct 20 '16 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). – Damien Flament Nov 15 '18 at 23:25
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
2

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.

  • 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 '16 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. – Damien Flament Nov 15 '18 at 23:33
0

This seems to work for me:

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

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.