I have a bash function defined in a global bashrc, which requires root privileges to work. How can I run it with sudo, e.g. sudo myfunction
. By default it gives an error:
sudo: myfunction: command not found
I have a bash function defined in a global bashrc, which requires root privileges to work. How can I run it with sudo, e.g. sudo myfunction
. By default it gives an error:
sudo: myfunction: command not found
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'
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"
Maybe you can do:
function meh() {
sudo -v
sudo cat /etc/shadow
}
This should work and saves you from typing sudo on the commandline.
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
# Wout.Mertens@gmail.com
#
# 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).
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.
#!/bin/bash
function smth() {
echo "{{"
whoami
echo "}}"
}
if [ $(whoami) != "root" ]; then
whoami
echo "i'm not root"
sudo $0
else
smth
fi
As pointed out by Legolas in the comments of the answer of Dennis Williamson you should read the answer of bmargulies on a similar question posted on stackoverflow.
Starting from that I wrote a function to cover this issue, which basically realizes the idea of bmargulies.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
# EXESUDO
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
#
# Purpose:
# -------------------------------------------------------------------- #
# Execute a function with sudo
#
# Params:
# -------------------------------------------------------------------- #
# $1: string: name of the function to be executed with sudo
#
# Usage:
# -------------------------------------------------------------------- #
# exesudo "funcname" followed by any param
#
# -------------------------------------------------------------------- #
# Created 01 September 2012 Last Modified 02 September 2012
function exesudo ()
{
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ##
#
# LOCAL VARIABLES:
#
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ##
#
# I use underscores to remember it's been passed
local _funcname_="$1"
local params=( "$@" ) ## array containing all params passed here
local tmpfile="/dev/shm/$RANDOM" ## temporary file
local filecontent ## content of the temporary file
local regex ## regular expression
local func ## function source
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ##
#
# MAIN CODE:
#
### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ##
#
# WORKING ON PARAMS:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Shift the first param (which is the name of the function)
unset params[0] ## remove first element
# params=( "${params[@]}" ) ## repack array
#
# WORKING ON THE TEMPORARY FILE:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
content="#!/bin/bash\n\n"
#
# Write the params array
content="${content}params=(\n"
regex="\s+"
for param in "${params[@]}"
do
if [[ "$param" =~ $regex ]]
then
content="${content}\t\"${param}\"\n"
else
content="${content}\t${param}\n"
fi
done
content="$content)\n"
echo -e "$content" > "$tmpfile"
#
# Append the function source
echo "#$( type "$_funcname_" )" >> "$tmpfile"
#
# Append the call to the function
echo -e "\n$_funcname_ \"\${params[@]}\"\n" >> "$tmpfile"
#
# DONE: EXECUTE THE TEMPORARY FILE WITH SUDO
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sudo bash "$tmpfile"
rm "$tmpfile"
}
Example of usage:
running the following snippet
#!/bin/bash
function exesudo ()
{
# copy here the previous exesudo function !!!
}
test_it_out ()
{
local params=( "$@" )
echo "Hello "$( whoami )"!"
echo "You passed the following params:"
printf "%s\n" "${params[@]}" ## print array
}
echo "1: calling without sudo"
test_it_out "first" "second"
echo ""
echo "2. calling with sudo"
exesudo test_it_out -n "john done" -s "done"
exit
Will output
calling without sudo
Hello yourname!
You passed the following params:
first
secondcalling with sudo
Hello root!
You passed the following params:
-n
john done
-s
foo
If you need to use this in a shell calling a function which is defined in your bashrc, as you asked, then you have to put the previous exesudo function on the same bashrc file as well, like the following:
function yourfunc ()
{
echo "Hello "$( whoami )"!"
}
export -f yourfunc
function exesudo ()
{
# copy here
}
export -f exesudo
Then you have to logout and login again or use
source ~/.bashrc
Finally you can use exesudo as follow:
$ yourfunc
Hello yourname!
$ exesudo yourfunc
Hello root!
It took me a few months to create this feature. Entirely transparent, it has the same behavior as the real sudo command, apart from adding an additional parameter:
It was necessary to create the following function first:
cmdnames () {
{ printf '%s' "$PATH" | xargs -d: -I{} -- find -L {} -maxdepth 1 -executable -type f -printf '%P\n' 2>/dev/null; compgen -b; } | sort -b | uniq
return 0
}
The function, completely created by me, is the following:
sudo () {
local flagc=0
local flagf=0
local i
if [[ $# -eq 1 && ( $1 == "-h" || ( --help == $1* && ${#1} -ge 4 ) ) ]]; then
command sudo "$@" | perl -lpe '$_ .= "\n -c, --run-command run command instead of the function if the names match" if /^ -C, / && ++$i == 1'
return ${PIPESTATUS[0]}
fi
for (( i=1; i<=$#; i++ )); do
if [[ ${!i} == -- ]]; then
i=$((i+1))
if [[ $i -gt $# ]]; then break; fi
else
if [[ ${!i} == --r ]]; then
command sudo "$@" 2>&1 | perl -lpe '$_ .= " '"'"'--run-command'"'"'" if /^sudo: option '"'"'--r'"'"' is ambiguous/ && ++$i == 1'
return ${PIPESTATUS[0]}
fi
if [[ ${!i} == -c || ( --run-command == ${!i}* && $(expr length "${!i}") -ge 4 ) ]]; then
flagf=-1
command set -- "${@:1:i-1}" "${@:i+1}"
i=$((i-1))
continue
fi
command sudo 2>&1 | grep -E -- "\[${!i} [A-Za-z]+\]" > /dev/null && { i=$((i+1)); continue; }
fi
cmdnames | grep "^${!i}$" > /dev/null && flagc=1
if [[ ! ( flagc -eq 1 && flagf -eq -1 ) ]]; then
if declare -f -- "${!i}" &> /dev/null; then flagf=1; fi
fi
break
done
if [[ $flagf -eq 1 ]]; then
command sudo "${@:1:i-1}" bash -sc "shopt -s extglob; $(declare -f); $(printf "%q " "${@:i}")"
else
command sudo "$@"
fi
return $?
}
Disadvantages: none. Latest update: 03/02/2022.
New Answer. Add this to your ~/.bashrc to run functions. As a bonus, it can run aliases too.
ssudo () # super sudo
{
[[ "$(type -t $1)" == "function" ]] &&
ARGS="$@" && sudo bash -c "$(declare -f $1); $ARGS"
}
alias ssudo="ssudo "