Jak mogę wykonać funkcję bash w sudo?

29

Mam funkcję bash zdefiniowaną w globalnym bashrc, która do działania wymaga uprawnień roota. Jak mogę uruchomić go z sudo, np sudo myfunction. Domyślnie wyświetla błąd:

sudo: moja funkcja: nie znaleziono polecenia

Eugene Yarmash
źródło
2
Nigdy nie próbowałem, ale ten post na blogu wydaje się sobie z tym poradzić: w00tbl0g.blogspot.com/2007/05/…
Grizly
instalacja powyższego skryptu wymaga „set alias sudo = sudowrap”, co nie jest zalecane imho. Proszę zobaczyć moją odpowiedź na rozwiązanie, które nie wymaga niczego do pracy.
Luca Borrione
Jest to jeden z wielu powodów, dla których funkcje powłoki są złe. Funkcje powłoki powinny być ograniczone do poleceń, które chcesz zmienić w swoim środowisku. Do końca używaj rzeczywistych skryptów. Jaka jest zaleta funkcji? (OK, „Nadużywanie” jest złe, nie działa same. Założę się, że istnieje wiele innych dobrych powodów. Ale powinny one być wyjątkiem, a nie regułą, podczas pisania skryptów.)
Jeff Learman

Odpowiedzi:

4

Luca uprzejmie wskazał mi to pytanie, oto moje podejście: Rozwiń funkcję / alias przed wywołaniem sudo i przekaż go w całości do sudo, nie potrzebujesz plików tymczasowych.

Wyjaśniono tutaj na moim blogu . Obsługa wielu ofert :-)

# 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

Jedyną wadą tego podejścia jest to, że rozszerza ono tylko funkcję, którą wywołujesz, a nie dodatkowe funkcje, do których się odwołujesz. Podejście Kyle'a prawdopodobnie lepiej sobie z tym radzi, jeśli odwołujesz się do funkcji, które są załadowane do twojego bashrc (pod warunkiem, że zostanie wykonane podczas bash -cwywołania).

w00t
źródło
W usłudze ServerFault najlepiej jest wyświetlać pożądany kod, a także link do strony zewnętrznej, aby użytkownicy nie musieli klikać, aby uzyskać potrzebne informacje, aby informacje te mogły przetrwać potencjalną śmierć witryn zewnętrznych.
Widoczny kompilator
15

Możesz włączyć exportfunkcję, aby była dostępna dla bash -cpodpowłoki lub skryptów, w których chcesz jej używać.

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

Edytować

Działa to dla bezpośrednich podpowłok, ale najwyraźniej sudonie przekazuje funkcji (tylko zmienne). Nawet przy użyciu różnych kombinacji setenv, env_keepi negując env_resetnie wydają się pomóc.

Edytuj 2

Jednak wydaje się, że su robi wsparcia eksportowane funkcje.

your_function () { echo 'Hello, World'; }
export -f your_function
su -c 'your_function'
Wstrzymano do odwołania.
źródło
2
+1, powiedziałbym, że to poprawna odpowiedź.
Kyle Brandt,
1
czy ta metoda działa? W moim przypadku tak nie jest.
pradeepchhetri
@pradeepchhetri Możesz podać więcej informacji, takich jak to, czego dokładnie próbujesz, której powłoki używasz i jakiego systemu operacyjnego używasz.
Legolas
@ Legolas: Próbuję tego samego, co skrypt napisany w powyższym skrypcie. Pojawia się błąd bash: your_function: command not found. Używam Ubuntu 11.04i bash shell.
pradeepchhetri
@pradeepchhetri co zrobić, jeśli używasz sudo -E bash -c 'your_function'?
Legolas
4

Może możesz zrobić:

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

Powinno to działać i oszczędzać Ci wpisywania sudo w wierszu poleceń.

wzzrd
źródło
1
W zależności od systemu ... wyświetli się monit o każde wywołanie polecenia sudo w celu wprowadzenia hasła ... lub monit raz i buforuj go. Lepiej byłoby wykryć, czy działasz jako root, a jeśli nie ... jeszcze raz wywołaj skrypt bash z sudo.
TheCompWiz
Nie spotkałem się jeszcze z systemem, który nie buforuje hasła sudo: domyślna wartość timestamp_timeout to 5. Jeśli ustawisz ją na 0, zawsze będziesz proszony o hasło, ale byłoby to ustawienie niestandardowe.
wzzrd
3

Jeśli chcesz wywołać funkcję w kontekście sudo, chcesz użyć declare:

#!/bin/bash

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

sudo su another_user -c "$(declare -f hello); hello"
ferdy
źródło
Działa to, o ile twoja funkcja nie wywołuje innych funkcji.
modiX
w moim przypadku jest to najlepsza odpowiedź.
Jim
2

Wykonałbym nową powłokę, uruchamiając samą sudo, wtedy funkcja będzie działać z uprawnieniami roota. Na przykład coś takiego:

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

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

Możesz nawet zrobić alias dla sudo bashlinii.

Kyle Brandt
źródło
2
#!/bin/bash

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

if [ $(whoami) != "root" ]; then
    whoami
    echo "i'm not root"
    sudo $0
else
    smth
fi
burz
źródło
2

Jak zauważył Legolas w komentarzach do odpowiedzi Dennisa Williamsona , powinieneś przeczytać odpowiedź bmargulies na podobne pytanie zadane na stackoverflow.

Poczynając od tego, napisałem funkcję obejmującą ten problem, która zasadniczo realizuje ideę 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"
}



Przykład użycia:
uruchomienie następującego fragmentu kodu

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



Wyjdzie

  1. dzwonienie bez sudo
    Witaj, twoje imię!
    Zdałeś następujące parametry:
    pierwsza
    sekunda

  2. dzwonienie z sudo
    Hello root!
    Przeszedłeś następujące parametry:
    -n
    john done
    -s
    foo



Jeśli musisz użyć tego w powłoce wywołującej funkcję zdefiniowaną w twoim bashrc, tak jak prosiłeś, musisz również umieścić poprzednią funkcję exesudo w tym samym pliku bashrc , podobnie jak poniżej:

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

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



Następnie musisz się wylogować i zalogować ponownie lub użyć

source ~/.bashrc



Wreszcie możesz użyć exesudo w następujący sposób:

$ yourfunc
Hello yourname!

$ exesudo yourfunc
Hello root!
Luca Borrione
źródło
Powraca /dev/shm/22481: No such file or directory.
modiX