I have a script that does a number of different things, most of which do not require any special privileges. However, one specific section, which I have contained within a function, needs root privileges.
I don't wish to require the entire script to run as root, and I want to be able to call this function, with root privileges, from within the script. Prompting for a password if necessary isn't an issue since it is mostly interactive anyway. However, when I try to use sudo functionx
, I get:
sudo: functionx: command not found
As I expected, export
didn't make a difference. I'd like to be able to execute the function directly in the script rather than breaking it out and executing it as a separate script for a number of reasons.
Is there some way I can make my function "visible" to sudo without extracting it, finding the appropriate directory, and then executing it as a stand-alone script?
The function is about a page long itself and contains multiple strings, some double-quoted and some single-quoted. It is also dependent upon a menu function defined elsewhere in the main script.
I would only expect someone with sudo ANY to be able to run the function, as one of the things it does is change passwords.
14 Answers 14
I will admit that there's no simple, intuitive way to do this, and this is a bit hackey. But, you can do it like this:
function hello()
{
echo "Hello!"
}
# Test that it works.
hello
FUNC=$(declare -f hello)
sudo bash -c "$FUNC; hello"
Or more simply:
sudo bash -c "$(declare -f hello); hello"
It works for me:
$ bash --version
GNU bash, version 4.3.42(1)-release (x86_64-apple-darwin14.5.0)
$ hello
Hello!
$
$ FUNC=$(declare -f hello)
$ sudo bash -c "$FUNC; hello"
Hello!
Basically, declare -f
will return the contents of the function, which you then pass to bash -c
inline.
If you want to export all functions from the outer instance of bash, change FUNC=$(declare -f hello)
to FUNC=$(declare -f)
.
Edit
To address the comments about quoting, see this example:
$ hello()
> {
> echo "This 'is a' test."
> }
$ declare -f hello
hello ()
{
echo "This 'is a' test."
}
$ FUNC=$(declare -f hello)
$ sudo bash -c "$FUNC; hello"
Password:
This 'is a' test.
-
3This only works by accident, because
echo "Hello!"
is effectively the same asecho Hello!
(i.e. double-quotes make no difference for this particular echo command). In many/most other circumstances, the double-quotes in the function are likely to break thebash -c
command.cas– cas2016年03月11日 05:21:59 +00:00Commented Mar 11, 2016 at 5:21 -
1This does answer the original question, so if I don't get a better solution I'll accept it. However, it does break my particular function (see my edit) since it's dependent on functions defined elsewhere in the script.BryKKan– BryKKan2016年03月11日 06:20:20 +00:00Commented Mar 11, 2016 at 6:20
-
1i did some testing earlier this afternoon (using
bash -xc
rather than justbash -c
) and it looks likebash
is smart enough to re-quote things in this situation, even to the extent of replacing double-quotes with single quotes and changing'
to'\''
if necessary. I'm sure there will be some cases it can't handle, but it definitely works for at least simple and moderately complex cases - e.g. tryfunction hello() { filename="this is a 'filename' with single quotes and spaces" ; echo "$filename" ; } ; FUNC=$(declare -f hello) ; bash -xc "$FUNC ; hello"
cas– cas2016年03月11日 12:07:13 +00:00Commented Mar 11, 2016 at 12:07 -
5@cas
declare -f
prints out the function definition in a way that can be re-parsed by bash, sobash -c "$(declare -f)"
does work correctly (assuming that the outer shell is also bash). The example you posted shows it working correctly — where the quotes were changed is in the trace, because bash prints out traces in shell syntax, e.g. trybash -xc 'echo "hello world"'
Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2016年03月11日 19:57:09 +00:00Commented Mar 11, 2016 at 19:57 -
3Excellent answer. I implemented your solution - I would note that you can import the script itself from within the script, provided you nest it within an conditional which checks if against
sudo yourFunction
being found (otherwise you get a segmentation error from the recursion)GrayedFox– GrayedFox2017年02月10日 12:38:56 +00:00Commented Feb 10, 2017 at 12:38
I've written my own Sudo
bash function to do that, it works to call functions and aliases :
function Sudo {
local firstArg=1ドル
if [ $(type -t $firstArg) = function ]
then
shift && command sudo bash -c "$(declare -f $firstArg);$firstArg $*"
elif [ $(type -t $firstArg) = alias ]
then
alias sudo='\sudo '
eval "sudo $@"
else
command sudo "$@"
fi
}
-
5This should be the accepted answer, Thank you so much @SebMa It is a brilliant solution!!AD Progress– AD Progress2022年02月06日 18:40:27 +00:00Commented Feb 6, 2022 at 18:40
-
@ADProgress Well, it was about 2 years too late to be the accepted answer, but a wonderful contribution nonetheless.BryKKan– BryKKan2023年06月07日 10:54:27 +00:00Commented Jun 7, 2023 at 10:54
-
1Here an Equiv function for
macos
where-t
option is not supported intype
command. unix.stackexchange.com/a/756361/318478 based on @SebMa functionMohamed Allal– Mohamed Allal2023年09月12日 22:08:24 +00:00Commented Sep 12, 2023 at 22:08
The "problem" is that sudo
clears the environment (except for a handful of allowed variables) and sets some variables to pre-defined safe values in order to protect against security risks. in other words, this is not actually a problem. It's a feature.
For example, if you set PATH="/path/to/myevildirectory:$PATH"
and sudo
didn't set PATH to a pre-defined value then any script that didn't specify the full pathname to ALL commands it runs (i.e. most scripts) would look in /path/to/myevildirectory
before any other directory. Put commands like ls
or grep
or other common tools in there and you can easily do whatever you like on the system.
The easiest / best way is to re-write the function as a script and save it somewhere in the path (or specify the full path to the script on the sudo
command line - which you'll need to do anyway unless sudo
is configured to allow you to run ANY command as root), and make it executable with chmod +x /path/to/scriptname.sh
Rewriting a shell function as a script is as simple as just saving the commands inside the function definition to a file (without the function ...
, {
and }
lines).
-
This does not answer the question in any way. He specifically wants to avoid putting it in a script.Will– Will2016年03月11日 05:37:13 +00:00Commented Mar 11, 2016 at 5:37
-
2Also
sudo -E
avoids clearing the environment.Will– Will2016年03月11日 05:37:36 +00:00Commented Mar 11, 2016 at 5:37 -
I understand to some extent why it is happening. I was hoping there was some means to temporarily override this behavior. Somewhere else a -E option was mentioned, though that didn't work in this case. Unfortunately, while I appreciate the explanation of how to make it a standalone script, that specifically doesn't answer the question, because I wanted a means to avoid that. I have no control over where the end user places the script and I'd like to avoid both hard-coded directories and the song and dance of trying to accurately determine where the main script was run from.BryKKan– BryKKan2016年03月11日 05:41:29 +00:00Commented Mar 11, 2016 at 5:41
-
1it doesn't matter whether that's what the OP asked for or not. If what he wants either won't work or can only be made to work by doing something extremely insecure then they need to be told that and provided with an alternative - even if the alternative is something they explicitly stated they don't want (because sometimes that's the only or the best way to do it safely). It would be irresponsible to tell someone how to shoot themselves in the foot without giving them warning about the likely consequences of pointing a gun at their feet and pulling the trigger.cas– cas2016年03月11日 05:43:32 +00:00Commented Mar 11, 2016 at 5:43
-
1@cas That is true. It can't be done securely is an acceptable answer in some circumstances. See my last edit though. I'd be curious to know if your opinion on the security implications is the same given that.BryKKan– BryKKan2016年03月11日 06:30:32 +00:00Commented Mar 11, 2016 at 6:30
My take on this, built upon other answers, but as far as I can see the only one properly handling function arguments and quoting:
sudo-function() {
(($#)) || { echo "Usage: sudo-function FUNC [ARGS...]" >&2; return 1; }
sudo bash -c "$(declare -f "1ドル");$(printf ' %q' "$@")"
}
$ args() { local i=0; while (($#)); do echo "$((++i))=1ドル"; shift; done; }
$ sudo-function args a 'b c' "d 'e'" 'f "g"'
1=a
2=b c
3=d 'e'
4=f "g"
And expanding it to also run on aliases, builtins, and executables in user's but not root's $PATH:
super-sudo() {
(($#)) || { echo "Usage: super-sudo CMD [ARGS...]" >&2; return 1; }
local def ftype; ftype=$(type -t 1ドル) ||
{ echo "not found: 1ドル" >&2; return 1; }
if [[ "$ftype" == "function" ]]; then def=$(declare -f "1ドル")
else def=$(declare -p PATH); fi # file or builtin
sudo bash -c "${def};$(printf ' %q' "$@")"
}
alias super-sudo='super-sudo ' # so it runs aliases too
As most (all?) answers, it has a few limitations:
- Does not work if FUNC calls other functions
- As
sudo
, it might not work as expected if mixed with redirections and process substitutions<
>>
,<()
, etc.
And a small bonus: bash-completion!
complete -A function sudo-function
complete -c super-sudo
You can combine functions and aliases
Example:
function hello_fn() {
echo "Hello!"
}
alias hello='bash -c "$(declare -f hello_fn); hello_fn"'
alias sudo='sudo '
then sudo hello
works
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 "
-
+1 for a great approach on running aliases, but notice this will not properly quote arguments to functionsMestreLion– MestreLion2022年06月04日 19:56:50 +00:00Commented Jun 4, 2022 at 19:56
-
@MestreLion Is there a way we can make it better?Rucent88– Rucent882022年06月27日 07:37:32 +00:00Commented Jun 27, 2022 at 7:37
-
Using
printf ' %q' "$@"
, see my answerMestreLion– MestreLion2022年06月27日 07:47:43 +00:00Commented Jun 27, 2022 at 7:47
Entirely transparent, it has the same behavior as the real sudo command, apart from adding an additional parameter:
It is 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
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 $?
}
Here's a variation on Will's answer. It involves an additional cat
process, but offers the comfort of heredoc. In a nutshell it goes like this:
f ()
{
echo ok;
}
cat <<EOS | sudo bash
$(declare -f f)
f
EOS
If you want more food for thought, try this:
#!/bin/bash
f ()
{
x="a b";
menu "$x";
y="difficult thing";
echo "a $y to parse";
}
menu ()
{
[ "1ドル" == "a b" ] &&
echo "here's the menu";
}
cat <<EOS | sudo bash
$(declare -f f)
$(declare -f menu)
f
EOS
The output is:
here's the menu
a difficult thing to pass
Here we've got the menu
function corresponding with the one in the question, which is "defined elsewhere in the main script". If the "elsewhere" means its definition has already been read at this stage when the function demanding sudo
is being executed, then the situation is analogous. But it might not have been read yet. There may be another function that will yet trigger its definition. In this case declare -f menu
has to be replaced with something more sophisticated, or the whole script corrected in a way that the menu
function is already declared.
-
Very interesting. I'll have to try it out at some point. And yes, the
menu
function would have been declared before this point, asf
is invoked from a menu.BryKKan– BryKKan2018年01月25日 23:54:31 +00:00Commented Jan 25, 2018 at 23:54 -
I don't get the output you show for the second case ('parse' not 'pass'). And the cat isn't needed,
sudo bash <<EOS
followed by the heredoc is equivalent (bash already implements heredocs and herestrings as pipes). But of course if anything in the function (directly or indirectly) reads stdin for user input, it won't work.dave_thompson_085– dave_thompson_0852025年07月03日 00:38:55 +00:00Commented Jul 3 at 0:38
I have a program which needs to be run as root
for part of it and not for the other part. I have a function inside it which tests the environment if root
is the user. If not, it invokes sudo
and recalls the script with all input parameters.
This keeps the first instance of the script (with the original user) on hold while it runs a second instance with root
as user. The next time it comes into this function it runs the functions that need to be run as root
. You could use the if
statement to run that part of the script which you don't want to have root
access. When it exits the 2nd instance of the program as root
, it comes back to this point in the 1st instance, and complete the script and exits.
The advantage of this method is that all variables are redefined again in the 2nd root
instance (Those which need to be preserved can be passed as input parameters). The variables in the 2nd instance in root
space are entirely separate and not visible to the 1st instance in user space, and vice versa.
The only hiccup is in the 2nd instance the original user is now defined by the environmental variable ${SUDO_USER}
, rather than ${USER}
, and the user's home directory needs to be constructed rather than using ${HOME}
, which now points to /root
, but that's all easy enough to get past.
I don't know if this is the best way to do it, but it works for me, and I've been using it for years.
{
function run_as_root {
local function_name="${FUNCNAME[0]}";
local return_value=0;
# notify entry to function
Echo-enter ${function_name};
# make sure we're running as root. Recall program as root if not.
if (( $EUID != 0 )); then
echo "Will now run as root...."; # - ${gc_sl_file}"
play_norm_sound;
# need to preserve HOME environmental variable as it's used for
# directory referencing, per change in Ubu 20.04
sudo --preserve-env=HOME -u root ${@};
return_value=$?;
if [ ${g_debug_mode} = true ]; then
echo "Returned from sudo call to self with return value: " \
"${return_value};" >> ${gc_logfile};
fi
# might not need this
# ensure return value is nonzero so we don't run program operations again
if [[ ${return_value} -ne 0 ]]; then
echo "We had a nonzero return value from root:${return_value}." | tee -a ${gc_logfile};
play_err_sound;
fi
else # we are already in root
# shift input parameters to get rid of script call as first parameter
shift
# run rest of program.
##### NOTE: This should be standard for all programs that use this function.
main ${@};
return_value=$?;
fi
Echo-return "${function_name}" "${return_value}";
return ${return_value}
}
}
The equiv of @SebMa answer. For macos
no -t
option for type
command.
function sudoRun {
local firstArg=1ドル
if [[ $(type $firstArg) == *function* ]]; then
shift && command sudo bash -c "$(declare -f $firstArg);$firstArg $*"
elif [[ $(type $firstArg) = *alias* ]]; then
alias sudo='\sudo '
eval "sudo $@"
else
command sudo "$@"
fi
}
-
The default shell on macOS is
zsh
, that's why there is notype -t
on macOS. Thanks for the adaptation.SebMa– SebMa2025年03月17日 14:46:38 +00:00Commented Mar 17 at 14:46
Assuming that your script is either (a) self-contained or (b) can source its components based on its location (rather than remembering where your home directory is), you could do something like this:
- use the
0ドル
pathname for the script, using that in thesudo
command, and pass along an option which the script will check, to call the password updating. As long as you rely on finding the script in the path (rather than just run./myscript
), you should get an absolute pathname in0ドル
. - since the
sudo
runs the script, it has access to the functions it needs in the script. - at the top of the script (rather, past the function declarations), the script would check its
uid
and realize that it was run as the root user, and seeing that it has the option set to tell it to update the password, go and do that.
Scripts can recur on themselves for various reasons: changing privileges is one of those.
My solution is to throw sudo as the last argument of the function or use the empty one:
f () {
local arg1=1ドル
local arg2=2ドル
local mysudo="3ドル" # can be empty if not given
$mysudo mycommand $arg1 $arg2
}
Function call:
f 1 2 # without sudo
f 1 2 sudo # with sudo
That is quite straightforward.
Just for the sake of showing more possibilities
Creating sudopass
script command
- We can create a script command
sudopass
in/usr/local/bin/sudopass
- source our aliases and functions scripts, in my case it's in
~/.shellrc
a shared script between bash and other shells like zsh
#!/bin/bash
source ~/.shellrc
# Check if a command was provided as an argument
if [ $# -eq 0 ]; then
echo "Usage: 0ドル <alias or function> [arguments]"
exit 1
fi
eval "$@"
- Set as executable
sudo chmod +x /usr/local/bin/sudopass
usage
sudo sudopass update_sudo_timeout 70
- execute perfectly
- The same function (this one is a function) that wasn't running. Now it does run with sudo just pretty well
Using export and setpriv also works
- Define a bash function
mywhoami
- export the function using
export -f
to make it visible to child processes - Call the function using
bash -c
as first test - Use
setpriv
callingbash
itself (including arguments)
Example
function mywhoami()
{
whoami
}
export -f mywhoami
setpriv --reuid=myuser bash -c "mywhoami"
Expected output: "myuser"
-
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.2025年05月12日 14:57:45 +00:00Commented May 12 at 14:57
declare
them too.sudo
:sudo
can't execute a shell function (other thansudo sh -c "shell_function"
whensh
knowsshell_function
by some means).