Bash: передає функцію як параметр


91

Мені потрібно передати функцію як параметр у Bash. Наприклад, такий код:

function x() {
  echo "Hello world"
}

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

around x

Повинно вивести:

before
Hello world
after

Я знаю, що evalце неправильно в цьому контексті, але це лише приклад :)

Будь-яка ідея?

Відповіді:


126

Якщо вам не потрібно нічого вигадливого, як затягування оцінки імені функції або її аргументів, вам не потрібно eval:

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

around x

роби те, що ти хочеш. Ви навіть можете передати функцію та її аргументи таким чином:

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

around x 1st 2nd

відбитки

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

2
Якщо у мене є інша функція y (), чи можу я зробити приблизно x 1st 2nd y 1st 2nd? Звідки він знає, що x та y є аргументами навколо, тоді як 1-й та 2-й - аргументи для x та y?
techguy2000

А ви можете опустити функціональне слово.
jasonleonhard

Однак тоді вони не будуть розставлені між іменами. тобто якщо у вас були методи всередині методів, і ви зберігаєте functionслово, ви не можете отримати доступ до цих внутрішніх методів, поки не запустите метод верхнього рівня.
jasonleonhard

29

Не думаю, що хтось цілком відповів на запитання. Він не запитував, чи може він повторювати струни в порядку. Швидше автор запитання хоче знати, чи може він імітувати поведінку покажчика функції.

Є кілька відповідей, схожих на те, що я б зробив, і я хочу розширити це на іншому прикладі.

Від автора:

function x() {
  echo "Hello world"
}

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

around x

Щоб розширити це, ми матимемо функцію x echo "Hello world: $ 1", щоб показати, коли дійсно виконується функція. Ми передамо рядок, який є назвою функції "x":

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

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

around x

Для опису цього рядок "x" передається функції around (), яка лунає "before", викликає функцію x (через змінну $ 1, перший параметр передається навколо) передаючи аргумент "ТУТ", нарешті echos after .

В іншому випадку, це методологія використання змінних як імен функцій. Змінні насправді містять рядок, що є ім'ям функції, і ($ variable arg1 arg2 ...) викликає функцію, що передає аргументи. Дивіться нижче:

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

Z="x"        # or just Z=x

($Z  10 20 30)

дає: 30 10 20, де ми виконали функцію з назвою "x", що зберігається у змінній Z і передає параметри 10 20 і 30.

Вище, де ми посилаємося на функції, присвоюючи їм імена змінних, щоб ми могли використовувати змінну замість фактичного знання імені функції (що схоже на те, що ви можете зробити в дуже класичній ситуації вказівника на функцію в c для узагальнення потоку програм, але попередньо -вибір функціональних викликів, які ви будете робити на основі аргументів командного рядка).

У bash це не вказівники на функції, а змінні, що посилаються на імена функцій, які ви згодом використовуєте.


Ця відповідь чудова. Я створив bash-сценарії всіх прикладів і запустив їх. Мені також дуже подобається, як ви зробили творців "лише змін", що дуже допомогло. У вашому останньому абзаці написано неправильне написання: "Вгорі"
JMI MADISON

Помилка виправлена. Спасибі @J MADISON
uDude

7
чому ви загортаєте його, ()чи це не запускає під оболонку?
horseyguy

17

немає необхідності використовувати eval

function x() {
  echo "Hello world"
}

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

around x

5

Ви не можете нічого передати функції, окрім рядків. Заміни процесу можуть начебто підробити. Bash має тенденцію утримувати FIFO відкритим до тих пір, поки команда, яку він не розширить до завершення.

Ось швидкий дурний

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)

Функції можна експортувати, але це не так цікаво, як здається спочатку. Я вважаю, що це в основному корисно для того, щоб зробити функції налагодження доступними для сценаріїв або інших програм, які запускають сценарії.

(
    id() {
        "$@"
    }

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

id як і раніше отримує лише рядок, який є іменем функції (автоматично імпортованої із серіалізації в середовищі) та її аргументами.

Коментар Pumbaa80 до іншої відповіді також хороший ( eval $(declare -F "$1")), але в основному корисний для масивів, а не функцій, оскільки вони завжди глобальні. Якби вам потрібно було запустити це в межах функції, все, що вона зробила б, це перевизначити її, тож ефекту немає. Він не може використовуватися для створення закриття або часткових функцій або "екземплярів функцій", залежно від того, що трапляється, пов'язане в поточній області дії. У кращому випадку це може бути використано для зберігання визначення функції в рядку, який перевизначається деінде - але ці функції також можуть бути жорстко закодовані, якщо звичайно evalне використовується

В основному Bash не можна використовувати таким чином.


2

Кращий підхід - використовувати локальні змінні у своїх функціях. Потім проблема полягає в тому, як ви отримаєте результат до абонента. Один із механізмів полягає у використанні заміни команд:

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

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

Тут результат виводиться на stdout, і виклик використовує заміну команди, щоб захопити значення у змінну. Потім змінну можна використовувати за потреби.


1

Ви повинні мати щось на зразок:

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

Потім можна зателефонувати around x


-1

eval - це, мабуть, єдиний спосіб досягти цього. Єдиний реальний мінус - це аспект безпеки, оскільки вам потрібно переконатись, що нічого шкідливого не передається, і будуть викликані лише ті функції, які ви хочете викликати (разом із перевіркою, що в ньому немає неприємних символів, таких як ';' в ньому також).

Отже, якщо ви закликаєте код, то eval - це, мабуть, єдиний спосіб це зробити. Зауважте, що існують і інші форми eval, які, ймовірно, працюють надто із залученням підкоманд ($ () та ``), але вони не безпечніші та дорожчі.


eval - це єдиний спосіб це зробити.
Вес,

1
Ви можете легко перевірити, чи eval $1буде викликати функцію за допомогоюif declare -F "$1" >/dev/null; then eval $1; fi
user123444555621

2
... або навіть краще:eval $(declare -F "$1")
user123444555621
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.