Como posso executar uma função bash com o sudo?

29

Eu tenho uma função bash definida em um bashrc global, que requer privilégios de root para funcionar. Como posso executá-lo com o sudo, por exemplo sudo myfunction. Por padrão, ele fornece um erro:

sudo: myfunction: comando não encontrado

Eugene Yarmash
fonte
2
Nunca tentei, mas este post do blog parece lidar com isso: w00tbl0g.blogspot.com/2007/05/…
Grizly
a instalação do script acima requer 'set alias sudo = sudowrap', que não é recomendável imho. Por favor, veja minha resposta para uma solução que não requer nada para funcionar.
Luca Borrione 2/12/12
Essa é uma das muitas razões pelas quais as funções de shell são más. As funções do shell devem ser limitadas aos comandos que você deseja alterar seu ambiente. Use scripts reais para o resto. Qual é a vantagem de uma função? (OK, "O uso excessivo" é mau, não funciona por si só. E aposto que existem muitas outras boas razões. Mas elas devem ser a exceção, e não a regra, ao escrever scripts.)
Jeff Learman

Respostas:

4

Luca gentilmente me apontou para esta pergunta, eis a minha abordagem: expanda a função / alias antes da chamada para o sudo e passe-a completamente para o sudo, sem necessidade de arquivos temporários.

Explicado aqui no meu blog . Há um monte de manipulação de cotações :-)

# 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

A única desvantagem dessa abordagem é que ela apenas expande a função que você está chamando, e não as funções extras que você está referenciando a partir daí. A abordagem de Kyle provavelmente lida com isso melhor se você estiver referenciando funções que são carregadas no seu bashrc (desde que sejam executadas na bash -cchamada).

w00t
fonte
No ServerFault, é preferível que você mostre o código desejado e o link para o site externo, para que os usuários não precisem clicar para obter as informações desejadas, para que as informações sobrevivam à morte potencial de sites externos.
Compilador conspícuo
15

Você pode exportsua função para disponibilizá-lo para um bash -csubshell ou scripts nos quais deseja usá-lo.

your_function () { echo 'Hello, World'; }
export -f your_function
bash -c 'your_function'

Editar

Isso funciona para subshells diretos, mas aparentemente sudonão encaminha funções (apenas variáveis). Mesmo usando várias combinações de setenv, env_keepe negando env_resetparecem não ajuda.

Editar 2

No entanto , parece que su ele suporta funções exportadas.

your_function () { echo 'Hello, World'; }
export -f your_function
su -c 'your_function'
Pausado até novo aviso.
fonte
2
+1, eu diria que esta é a resposta correta.
Kyle Brandt
1
se esse método funciona? No meu caso, não é.
26612 pradeepchhetri #
@pradeepchhetri Você pode fornecer mais informações, como o que está tentando com precisão, qual shell você usa e qual sistema operacional você usa.
precisa
@ Legolas: Eu estou tentando a mesma coisa que o script escreveu no script acima. Estou recebendo o erro bash: your_function: command not found. Eu estou usando Ubuntu 11.04e bash shell.
26612 pradeepchhetri
@pradeepchhetri what if you use sudo -E bash -c 'your_function'?
Legolas
4

Talvez você possa fazer:

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

Isso deve funcionar e evita que você digite sudo na linha de comando.

wzzrd
fonte
1
Dependendo do seu sistema ... isso solicitará que cada chamada do comando sudo insira a senha ... ou solicite uma vez e armazene em cache. Seria melhor detectar se você está executando como root e, se não estiver ... chame o script bash novamente com o sudo uma vez.
TheCompWiz
Ainda não encontrei um sistema que não armazene em cache a senha do sudo: o padrão para timestamp_timeout é 5. Se você configurá-lo como 0, sempre será solicitada uma senha, mas essa seria uma configuração personalizada.
Wzzrd
3

Se você precisar chamar uma função no contexto de um sudo, você deseja usar declare:

#!/bin/bash

function hello() {
  echo "Hello, $USER"
}

sudo su another_user -c "$(declare -f hello); hello"
ferdy
fonte
Isso funciona, desde que sua função não chame outras funções.
ModiX 7/0318
para o meu caso de uso, esta é a melhor resposta.
Jim
2

Eu executaria um novo shell fazendo o sudo executar o próprio shell, então a função será executada com privilégios de root. Por exemplo, algo como:

vim myFunction
#The following three lines go in myFunction file
function mywho {
    sudo whoami
}

sudo bash -c '. /home/kbrandt/myFunction; mywho'
root

Você poderia, então, criar um alias para a sudo bashlinha também.

Kyle Brandt
fonte
2
#!/bin/bash

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

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

Como apontado por Legolas nos comentários da resposta de Dennis Williamson, você deve ler a resposta de bmargulies em uma pergunta semelhante postada no stackoverflow.

A partir disso, escrevi uma função para cobrir esta questão, que basicamente realiza a ideia de 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"
}



Exemplo de uso:
executando o seguinte 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



Saída

  1. chamando sem sudo
    Olá, seu nome!
    Você passou os seguintes parâmetros:
    primeiro
    segundo

  2. chamando com sudo
    Olá root!
    Você passou os seguintes parâmetros:
    -n
    john done
    -s
    foo



Se você precisar usá-lo em um shell que chame uma função definida em seu bashrc, conforme solicitado, será necessário colocar a função exesudo anterior no mesmo arquivo bashrc , como o seguinte:

function yourfunc ()
{
echo "Hello "$( whoami )"!"
}
export -f yourfunc

function exesudo ()
{
   # copy here
}
export -f exesudo



Então você precisa sair e fazer login novamente ou usar

source ~/.bashrc



Finalmente, você pode usar o exesudo da seguinte maneira:

$ yourfunc
Hello yourname!

$ exesudo yourfunc
Hello root!
Luca Borrione
fonte
Retorna /dev/shm/22481: No such file or directory.
ModiX 7/0318