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.

Tags:

Bash

Sudo