How can I execute a bash function with sudo?
Solution 1:
You can export
your function to make it available to a bash -c
subshell or scripts that you want to use it in.
your_function () { echo 'Hello, World'; }
export -f your_function
bash -c 'your_function'
Edit
This works for direct subshells, but apparently sudo
doesn't forward functions (only variables). Even using various combinations of setenv
, env_keep
and negating env_reset
don't seem to help.
Edit 2
However, it appears that su
does support exported functions.
your_function () { echo 'Hello, World'; }
export -f your_function
su -c 'your_function'
Solution 2:
If you need to call a function in the context of a sudo, you want to use declare
:
#!/bin/bash
function hello() {
echo "Hello, $USER"
}
sudo su another_user -c "$(declare -f hello); hello"
Solution 3:
Maybe you can do:
function meh() {
sudo -v
sudo cat /etc/shadow
}
This should work and saves you from typing sudo on the commandline.
Solution 4:
Luca kindly pointed me to this question, here's my approach: Expand the function/alias before the call to sudo and pass it in its entirety to sudo, no temp files needed.
Explained here on my blog. There's lots of quote handling :-)
# Wrap sudo to handle aliases and functions
# [email protected]
#
# Accepts -x as well as regular sudo options: this expands variables as you not root
#
# Comments and improvements welcome
#
# Installing: source this from your .bashrc and set alias sudo=sudowrap
# You can also wrap it in a script that changes your terminal color, like so:
# function setclr() {
# local t=0
# SetTerminalStyle $1
# shift
# "$@"
# t=$?
# SetTerminalStyle default
# return $t
# }
# alias sudo="setclr sudo sudowrap"
# If SetTerminalStyle is a program that interfaces with your terminal to set its
# color.
# Note: This script only handles one layer of aliases/functions.
# If you prefer to call this function sudo, uncomment the following
# line which will make sure it can be called that
#typeset -f sudo >/dev/null && unset sudo
sudowrap ()
{
local c="" t="" parse=""
local -a opt
#parse sudo args
OPTIND=1
i=0
while getopts xVhlLvkKsHPSb:p:c:a:u: t; do
if [ "$t" = x ]; then
parse=true
else
opt[$i]="-$t"
let i++
if [ "$OPTARG" ]; then
opt[$i]="$OPTARG"
let i++
fi
fi
done
shift $(( $OPTIND - 1 ))
if [ $# -ge 1 ]; then
c="$1";
shift;
case $(type -t "$c") in
"")
echo No such command "$c"
return 127
;;
alias)
c="$(type "$c")"
# Strip "... is aliased to `...'"
c="${c#*\`}"
c="${c%\'}"
;;
function)
c="$(type "$c")"
# Strip first line
c="${c#* is a function}"
c="$c;\"$c\""
;;
*)
c="\"$c\""
;;
esac
if [ -n "$parse" ]; then
# Quote the rest once, so it gets processed by bash.
# Done this way so variables can get expanded.
while [ -n "$1" ]; do
c="$c \"$1\""
shift
done
else
# Otherwise, quote the arguments. The echo gets an extra
# space to prevent echo from parsing arguments like -n
while [ -n "$1" ]; do
t="${1//\'/\'\\\'\'}"
c="$c '$t'"
shift
done
fi
echo sudo "${opt[@]}" -- bash -xvc \""$c"\" >&2
command sudo "${opt[@]}" bash -xvc "$c"
else
echo sudo "${opt[@]}" >&2
command sudo "${opt[@]}"
fi
}
# Allow sudowrap to be used in subshells
export -f sudowrap
The one disadvantage to this approach is that it only expands the function you're calling, not any extra functions you're referencing from there. Kyle's approach probably handles that better if you're referencing functions that are loaded in your bashrc (provided it gets executed on the bash -c
call).
Solution 5:
I would execute a new shell by having sudo execute the shell itself, then the function will run with root privileges. For example something like:
vim myFunction
#The following three lines go in myFunction file
function mywho {
sudo whoami
}
sudo bash -c '. /home/kbrandt/myFunction; mywho'
root
You could even then go to make an alias for the sudo bash
line as well.