Bash: przekazanie funkcji jako parametru


91

Muszę przekazać funkcję jako parametr w Bash. Na przykład następujący kod:

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  eval $1
  echo "after"
}

around x

Powinien wynik:

before
Hello world
after

Wiem, że evalnie jest poprawne w tym kontekście, ale to tylko przykład :)

Dowolny pomysł?

Odpowiedzi:


126

Jeśli nie potrzebujesz niczego wymyślnego, jak opóźnianie oceny nazwy funkcji lub jej argumentów, nie potrzebujesz eval:

function x()      { echo "Hello world";          }
function around() { echo before; $1; echo after; }

around x

robi co chcesz. Możesz nawet przekazać funkcję i jej argumenty w ten sposób:

function x()      { echo "x(): Passed $1 and $2";  }
function around() { echo before; "$@"; echo after; }

around x 1st 2nd

wydruki

before
x(): Passed 1st and 2nd
after

2
Jeśli mam inną funkcję y (), czy mogę zrobić około x 1. 2. y 1. 2.? Skąd wie, że x i y są argumentami dla około, podczas gdy 1 i 2 to argumenty dla x i y?
techguy2000

Możesz pominąć słowo funkcyjne.
jasonleonhard

Jednak wtedy nie będą one rozdzielane nazwami. tj. jeśli masz metody wewnątrz metod i zachowasz functionsłowo, nie możesz uzyskać dostępu do tych wewnętrznych metod, dopóki nie uruchomisz metody najwyższego poziomu.
jasonleonhard

29

Myślę, że nikt nie odpowiedział na to pytanie. Nie zapytał, czy mógłby powtórzyć kolejno struny. Raczej autor pytania chce wiedzieć, czy może symulować zachowanie wskaźnika funkcji.

Jest kilka odpowiedzi, które są bardzo podobne do tego, co bym zrobił, i chcę je rozszerzyć o inny przykład.

Od autora:

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  ($1)                   <------ Only change
  echo "after"
}

around x

Aby to rozwinąć, będziemy mieć funkcję x echo „Witaj świecie: $ 1”, która będzie pokazywać, kiedy naprawdę następuje wykonanie funkcji. Przekażemy ciąg będący nazwą funkcji „x”:

function x() {
  echo "Hello world:$1"
}

function around() {
  echo "before"
  ($1 HERE)                   <------ Only change
  echo "after"
}

around x

Aby to opisać, ciąg „x” jest przekazywany do funkcji around (), która wywołuje echo „przed”, wywołuje funkcję x (poprzez zmienną $ 1, pierwszy parametr przekazany dookoła), przekazując argument „TUTAJ”, a na końcu odbija się echem po .

Poza tym jest to metodologia używania zmiennych jako nazw funkcji. W rzeczywistości zmienne zawierają łańcuch będący nazwą funkcji i ($ zmienna arg1 arg2 ...) wywołuje funkcję przekazującą argumenty. Zobacz poniżej:

function x(){
    echo $3 $1 $2      <== just rearrange the order of passed params
}

Z="x"        # or just Z=x

($Z  10 20 30)

daje: 30 10 20, gdzie wykonaliśmy funkcję o nazwie "x" przechowywaną w zmiennej Z i przekazaliśmy parametry 10 20 i 30.

Powyżej, w którym odwołujemy się do funkcji, przypisując im nazwy zmiennych, abyśmy mogli użyć zmiennej zamiast znajomości nazwy funkcji (co jest podobne do tego, co można zrobić w bardzo klasycznej sytuacji wskaźnika funkcji w c w celu uogólnienia przepływu programu, ale przed -wybierając wywołania funkcji, które będziesz wykonywać na podstawie argumentów wiersza poleceń).

W bashu nie są to wskaźniki funkcji, ale zmienne, które odnoszą się do nazw funkcji, których później użyjesz.


Ta odpowiedź jest niesamowita. Zrobiłem skrypty bash ze wszystkich przykładów i uruchomiłem je. Bardzo podoba mi się też sposób, w jaki stworzyłeś twórców „jedynej zmiany”, co bardzo pomogło. Twój przedostatni akapit zawiera błędną pisownię: „Aabove”
JMI MADISON

Naprawiono literówkę. Dzięki @J MADISON
uDude

7
dlaczego to zawijasz, czy ()to nie rozpocznie podpowłoki?
horseyguy

17

nie ma potrzeby używania eval

function x() {
  echo "Hello world"
}

function around() {
  echo "before"
  var=$($1)
  echo "after $var"
}

around x

5

Nie możesz przekazać niczego do funkcji innej niż ciągi znaków. Zastępowanie procesów może w pewnym sensie to udawać. Bash ma tendencję do przytrzymywania otwartego FIFO do czasu zakończenia jego rozwinięcia.

Oto szybki, głupiutki

foldl() {
    echo $(($(</dev/stdin)$2))
} < <(tr '\n' "$1" <$3)

# Sum 20 random ints from 0-999
foldl + 0 <(while ((n=RANDOM%999,x++<20)); do echo $n; done)

Funkcje można eksportować, ale nie jest to tak interesujące, jak się początkowo wydaje. Uważam, że jest to przydatne głównie do udostępniania funkcji debugowania skryptom lub innym programom, które uruchamiają skrypty.

(
    id() {
        "$@"
    }

    export -f id
    exec bash -c 'echowrap() { echo "$1"; }; id echowrap hi'
)

id nadal pobiera tylko ciąg, który jest nazwą funkcji (automatycznie importowany z serializacji w środowisku) i jej argumenty.

Komentarz Pumba80 do innej odpowiedzi jest również dobry ( eval $(declare -F "$1")), ale jest przydatny głównie w przypadku tablic, a nie funkcji, ponieważ są one zawsze globalne. Gdybyś miał to uruchomić w ramach funkcji, wszystko, co zrobiłby, to przedefiniowanie jej, więc nie ma żadnego efektu. Nie można go używać do tworzenia domknięć, funkcji częściowych lub „instancji funkcji” zależnych od tego, co jest związane w bieżącym zakresie. W najlepszym przypadku można to wykorzystać do przechowywania definicji funkcji w łańcuchu, który zostanie przedefiniowany w innym miejscu - ale te funkcje również mogą być zakodowane na stałe, chyba że oczywiście evalsą używane

Zasadniczo Bash nie może być używany w ten sposób.


2

Lepszym podejściem jest użycie zmiennych lokalnych w funkcjach. Problem polega zatem na tym, jak przekazać wynik dzwoniącemu. Jednym z mechanizmów jest użycie zastępowania poleceń:

function myfunc()
{
    local  myresult='some value'
    echo "$myresult"
}

result=$(myfunc)   # or result=`myfunc`
echo $result

Tutaj wynik jest wyprowadzany na standardowe wyjście, a obiekt wywołujący używa podstawiania poleceń, aby przechwycić wartość w zmiennej. Zmiennej można następnie użyć w razie potrzeby.


1

Powinieneś mieć coś w stylu:

function around()
{
  echo 'before';
  echo `$1`;
  echo 'after';
}

Możesz wtedy zadzwonić around x


-1

eval jest prawdopodobnie jedynym sposobem, aby to osiągnąć. Jedynym prawdziwym minusem jest aspekt bezpieczeństwa, ponieważ musisz się upewnić, że nic złośliwego nie zostanie przekazane i zostaną wywołane tylko funkcje, które chcesz, aby zostały wywołane (wraz z sprawdzeniem, czy nie ma nieprzyjemnych znaków, takich jak ';') w nim również).

Więc jeśli to ty wywołujesz kod, eval jest prawdopodobnie jedynym sposobem, aby to zrobić. Zauważ, że istnieją inne formy ewaluacji, które prawdopodobnie działałyby również z wykorzystaniem podpoleceń ($ () i ``), ale nie są one bezpieczniejsze i są droższe.


eval to jedyny sposób, aby to zrobić.
Wes

1
Możesz łatwo sprawdzić, czy eval $1wywołałby funkcję za pomocąif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621

2
... lub nawet lepiej:eval $(declare -F "$1")
user123444555621
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.