34

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

3
  • 2
    Never tried, but this blog post seems to handle it: w00tbl0g.blogspot.com/2007/05/…
    – Grizly
    Sep 3, 2010 at 12:16
  • the installation of the above script require 'set alias sudo=sudowrap' which is not recommendable imho. Please see my answer for a solution which doesn't require anything to work. Sep 2, 2012 at 10:24
  • This is one of the many reasons that shell functions are evil. Shell functions should be limited to commands that you want to change your environment. Use actual scripts for the rest. What's the advantage of a function? (OK, "Overuse" is evil, not functions themselves. And I bet there are many other good reasons. But they should be the exception, not the rule, when writing scripts.) Oct 16, 2019 at 21:06

9 Answers 9

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
# 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).

4
  • On ServerFault, it's preferred you show the code that's desired as well as linking to the external site, so users don't have to click away to get the information they want, and so the information survives the potential death of external sites. Jan 15, 2015 at 22:23
  • I'm sure this is out-dated as this sudowrap function isn't working for me. Anyone have an updated version? Apr 4, 2021 at 16:07
  • @AndrewLamarra what's not working? I still use this.
    – w00t
    Apr 5, 2021 at 10:00
  • @w00t I copied this into my .bashrc file at the end and added a test function func() { echo Hello; }. But when I open a new terminal and test it out with sudowrap func, I get bash: line 8: $'\nfunc () \n{ \n echo Hello\n}': command not found. Been messing around with it a lot to try to fix it. Apr 5, 2021 at 11:59
20

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'
11
  • 2
    +1 , I would say this is the correct answer. Sep 6, 2010 at 17:52
  • 1
    whether this method works ?? In my case it is not. Mar 26, 2012 at 12:04
  • 1
    @pradeepchhetri You may want to give more information, such as what you are precisely trying, which shell you use, and which OS you use.
    – Legolas
    Mar 26, 2012 at 13:23
  • @Legolas: I am trying the same thing script wrote in the above script. I am getting the error bash: your_function: command not found. I am using Ubuntu 11.04 and bash shell. Mar 26, 2012 at 13:57
  • @pradeepchhetri what if you use sudo -E bash -c 'your_function'?
    – Legolas
    Mar 26, 2012 at 14:33
7

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"
2
  • This works, as long as your function does not call another functions. Mar 7, 2018 at 17:59
  • for my use-case, this is the best answer.
    – Jim
    Nov 16, 2018 at 2:21
5

Maybe you can do:

function meh() {
    sudo -v
    sudo cat /etc/shadow
}

This should work and saves you from typing sudo on the commandline.

2
  • 1
    Depending on your system... this will prompt you for every call of the sudo command to enter the password... or prompt you once & cache it. It would be better to detect if you're running as root, and if not... call the bash script again with sudo once.
    – TheCompWiz
    Sep 3, 2010 at 14:00
  • I have yet to encounter a system that does not cache the sudo password: the default for timestamp_timeout is 5. If you set it to 0, you are always asked for a password, but that would be a custom setting.
    – wzzrd
    Sep 3, 2010 at 14:36
2

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.

2
#!/bin/bash

function smth() {
    echo "{{"
    whoami
    echo "}}"
}

if [ $(whoami) != "root" ]; then
    whoami
    echo "i'm not root"
    sudo $0
else
    smth
fi
2

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

  1. calling without sudo
    Hello yourname!
    You passed the following params:
    first
    second

  2. calling 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!
2
  • It returns /dev/shm/22481: No such file or directory. Mar 7, 2018 at 16:31
  • This is usually not a problem since a script that requires root will run code that requires root privileges, but it's trivial to alias whoami to "echo root". maybe use /usr/bin/whoami ?
    – MSpreij
    Sep 20, 2021 at 13:55
1

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:

enter image description here

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.

0

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 "

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.