Pregunta ¿Cómo puedo ejecutar una función bash con sudo?


Tengo una función bash definida en un bashrc global, que requiere privilegios de root para funcionar. ¿Cómo puedo ejecutarlo con sudo, por ejemplo, sudo myfunction. Por defecto da un error:

sudo: myfunction: comando no encontrado


25
2017-09-03 12:05


origen


Nunca lo intenté, pero esta entrada de blog parece manejarlo: w00tbl0g.blogspot.com/2007/05/… - Grizly
la instalación del script anterior requiere 'set alias sudo = sudowrap', que no es recomendable imho. Por favor vea mi respuesta para una solución que no requiere nada para funcionar. - Luca Borrione


Respuestas:


Luca amablemente me señaló esta pregunta, aquí está mi enfoque: expandir la función / alias antes de la llamada a sudo y pasarla en su totalidad a sudo, no se necesitan archivos temporales.

Explicado aqui en mi blog. Hay un montón de manejo de cotización :-)

# 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

La única desventaja de este enfoque es que solo expande la función que está llamando, no las funciones adicionales a las que hace referencia desde allí. El enfoque de Kyle probablemente lo maneje mejor si hace referencia a las funciones que están cargadas en su base (siempre que se ejecute en la base de datos). bash -c llamada).


4
2017-09-03 13:23



En ServerFault, se prefiere que muestre el código deseado así como el enlace al sitio externo, para que los usuarios no tengan que hacer clic para obtener la información que desean, y así la información sobreviva a la posible muerte de sitios externos. - Conspicuous Compiler
@ConspicuousCompiler gracias por la pista, hecho. - w00t


Usted puede export su función para que esté disponible para una bash -c subshell o scripts en los que desea utilizarlo.

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

Editar

Esto funciona para subshells directos, pero aparentemente sudo No reenvía funciones (solo variables). Incluso utilizando varias combinaciones de setenv, env_keep y negando env_reset no parecen ayudar

Editar 2

sin embargo, parece que su  hace Apoyo a las funciones exportadas.

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

14
2017-09-03 14:21



+1, diría que esta es la respuesta correcta. - Kyle Brandt♦
Si este método funciona? En mi caso no lo es. - pradeepchhetri
@pradeepchhetri Es posible que desee proporcionar más información, como qué está intentando exactamente, qué shell utiliza y qué sistema operativo utiliza. - Legolas
@Legolas: Estoy intentando lo mismo que el script escribió en el script anterior. Estoy recibiendo el error bash: your_function: command not found. estoy usando Ubuntu 11.04 y bash shell. - pradeepchhetri
@pradeepchhetri qué pasa si usas sudo -E bash -c 'your_function'? - Legolas


Tal vez puedas hacer:

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

Esto debería funcionar y evita que escribas sudo en la línea de comandos.


4
2017-09-03 12:41



Dependiendo de su sistema ... esto le pedirá que cada llamada del comando sudo ingrese la contraseña ... o una vez que la guarde en caché. Sería mejor detectar si se está ejecutando como root y, si no, ... vuelva a llamar al script bash con sudo una vez. - TheCompWiz
Todavía no he encontrado un sistema que no almacene en caché la contraseña de sudo: el valor predeterminado para timestamp_timeout es 5. Si la configura en 0, siempre se le pedirá una contraseña, pero esa sería una configuración personalizada. - wzzrd


Ejecutaría un nuevo shell haciendo que sudo ejecute el propio shell, luego la función se ejecutará con privilegios de root. Por ejemplo algo como:

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

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

Incluso podrías ir a hacer un alias para el sudo bash la línea también.


2
2017-09-03 13:14





#!/bin/bash

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

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

2
2018-06-18 16:46





Como señaló Legolas en los comentarios de la respuesta de Dennis Williamson deberías leer la respuesta de bmargulies en una pregunta similar publicada en stackoverflow.

A partir de eso escribí una función para cubrir este problema, que básicamente se da cuenta de la idea 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"
}



Ejemplo de uso:
ejecutando el siguiente fragmento

#!/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



Saldrá

  1. llamando sin sudo
      Hola tu nombre!
      Pasaste los siguientes params:
      primero
      segundo

  2. llamando con sudo
      Hola root
      Pasaste los siguientes params:
      -norte
      John ha terminado
      -s
      foo



Si necesita usar esto en un shell que llame a una función que está definida en su bashrc, como pidió, entonces debe poner la función exesudo anterior en la misma Bashrc archivo también, como el siguiente:

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

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



Luego tienes que cerrar sesión e iniciar sesión de nuevo o usar

source ~/.bashrc



Finalmente puedes usar exesudo como sigue:

$ yourfunc
Hello yourname!

$ exesudo yourfunc
Hello root!

2
2017-09-01 18:14



Vuelve /dev/shm/22481: No such file or directory. - modiX


Si necesita llamar a una función en el contexto de un sudo, desea utilizar declare:

#!/bin/bash

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

sudo su another_user -c "$(declare -f hello); hello"

1
2018-03-01 15:33



Esto funciona, siempre que su función no llame a otras funciones. - modiX