Передача кодового блока как анона. функция

9

Можно ли рассматривать блок команд как анонимную функцию?

function wrap_this {
   run_something
   # Decide to run block or maybe not.
   run_something else
}

wrap_this {
   do_something
   do_somthing else
}

# Do something else

wrap_this {
   do_something_else_else
   do_something_else_else_else
}

(Я понимаю, что вы создаете функцию или файл для каждого блока, но я нахожу эту опцию более ясной и удобной для чтения в определенных ситуациях.)

whileделает это с do/doneи functionделает это с { multiple lines }. Я понимаю, что BASH не имеет анонимных функций, но возможно ли передать несколько команд другой функции, как вы можете сделать при определении функции или while?

dgo.a
источник
Вы имеете в виду, что хотите украсить (на языке Python) - т.е. вернуть функцию из функции? Ваш пример, синтаксически, даже не BASH: должен ли wrap_this быть функцией или вызовом функции?
Мел Бойс
Мне неясно, что вы хотите сделать. Как указал Мел, то, что вы написали, даже синтаксически допустимо, но мне неясно, как то, что вы написали, относится к анонимным функциям.
Крис Даун

Ответы:

2

Это самое короткое решение, о котором я мог подумать:

Учитывая эти функции:

# List processing
map() { while IFS='' read -r x; do "$@" "$x"; done; }
filter() { while IFS='' read -r x; do "$@" "$x" >&2 && echo "$x"; done; }
foldr() { local f="$1"; local result="$2"; shift 2;  while IFS='' read -r x; do result="$( "$f" "$@" "$x" "$result" )"; done; echo "$result"; }
foldl() { local f="$1"; local result="$2"; shift 2;  while IFS='' read -r x; do result="$( "$f" "$@" "$result" "$x" )"; done; echo "$result"; }

# Helpers
re() { [[ "$2" =~ $1 ]]; }

Примеры:

# Example helpers
toLower() { tr '[:upper:]' '[:lower:]'; }
showStructure() { [[ "$1" == "--curly" ]] && echo "{$2; $3}" || echo "($1, $2)"; }

# All lib* directories, ignoring case, using regex
ls /usr | map toLower | filter re 'lib.*'

# All block devices. (Using test, for lack of a full bash [[ … ]].)
cd /dev; ls | filter test -b

# Show difference between foldr and foldl
$ ls / | foldr showStructure '()'
(var/, (usr/, (tmp/, (sys/, (sbin/, (run/, (root/, (proc/, (opt/, (mnt/, (media/, (lost+found/, (lib64/, (lib32/, (lib@, (home/, (etc/, (dev/, (daten/, (boot/, (bin/, ())))))))))))))))))))))
$ ls / | foldr showStructure '{}' --curly
{var/; {usr/; {tmp/; {sys/; {sbin/; {run/; {root/; {proc/; {opt/; {mnt/; {media/; {lost+found/; {lib64/; {lib32/; {lib@; {home/; {etc/; {dev/; {daten/; {boot/; {bin/; {}}}}}}}}}}}}}}}}}}}}}}

(Эти примеры, конечно, являются просто примерами использования, и в действительности этот стиль имеет смысл только для более сложных вариантов использования.)

Как правило, всегда можно использовать следующий стиль:

f() { something "$@"       ; }; someList    | map    f
g() { something "$1" "$2" …; }; someCommand | filter g
                                               

Это не совсем лямбда, но это очень, очень близко. Всего несколько лишних символов.

Но ни одно из следующих сокращений удобства не работает, насколько я могу сказать:

λ() { [[ $@ ]]; } # fails on spaces
λ() { [[ "${@:-1}" ${@:1:-1} ]]; } # syntax error
alias λ=test # somehow ignored

К сожалению, bashне очень хорошо подходит для этого стиля, хотя некоторые его особенности имеют очень функциональный стиль.

Evi1M4chine
источник
Особенности bash имеют функциональный стиль? Единственное, что удаленно функционирует, - это то, что bash (как и почти любой язык оболочки) поддерживает форму композиции функций, направляя вывод одной команды на вход другой. Это скорее особенность общего дизайна Unix, а не bash как такового.
Кирилл
4

Мне удалось сделать то, что вы хотите с помощью evalвзлома. При этом я предупредил, что eval небезопасен, и вам следует избегать любой ценой . Сказав это, если вы уверены, что ваш код не будет злоупотреблять, вы можете использовать это:

wrap_this(){
    run_something
    eval "$(cat /dev/stdin)"
    run_something_else
}

Это позволяет запускать код следующим образом:

wrap_this << EOF
    my function
EOF

Не совсем идеально, поскольку внутренний блок является строкой, но создает повторное использование.

hkupty
источник
Это круто! Я бы просто добавил кавычки вокруг первого, !!чтобы предотвратить подстановку в heredoc. stackoverflow.com/questions/27920806/…
Saintali
3

Вы можете поместить код в строку и передать его evalили shили просто интерполировать его.

perform () {
  "$@"
}

perform echo "moo"

Тем не менее, вы можете быстро обернуться неприятностями.

tripleee
источник
1
На самом деле я искал perform { echo "moo" \n echo "moo moo" \n echo "moo moo moo" }. Я уже знал, что вы можете передать одну команду. Но я ищу несколько строк, а не только одну команду или одну строку. Спасибо за попытку.
dgo.a
3

Нет, Bash не имеет анонимных функций. Тем не менее, можно передать имя функции и аргументы в виде строк и заставить ее вызывать bash.

function wrap() {
    do_before
    "$@"
    do_after
}

wrap do_something with_arguments

Это, однако, несколько ограничено. Работа с цитированием может стать проблемой. Передача более чем одной команды также является осложнением.

Дэвид Баггерман
источник
1
Не nightname, все , что вам нужно сделать , это изменение $*в"$@"
Glenn Джекман
Многолинейные тела функций должны нормально работать с этим методом.
Гленн, я забыл об этой форме, спасибо. Это не полностью решает проблему цитирования. Эван, единственная команда, заключенная в несколько строк, будет работать нормально, я имел в виду иметь более одной команды в теле в соответствии с примерами в вопросе. Я обновлю свой ответ на оба комментария.
Дэвид Баггерман
Эта форма работает очень хорошо, если вы позаботитесь об определении функций, которые принимают простые аргументы, а затем передаете вызовы этих функций в качестве аргументов. То есть wrap my_func "$arg1" "$arg2". Одно из моих замечаний в этом примере заключается в том, что возвращаемое значение «$ @» потеряно, но это легко исправить. Может быть желательно обернуть "$ @", ()чтобы избежать изменений в окружающей среде при некоторых затратах на производительность.
Майкл Мол