Я уже несколько раз обсуждал, как и почему методы, описанные ниже, работают, поэтому я не буду делать это снова. Лично мои любимые по теме здесь и здесь .
Если вам не интересно читать это, но все же любопытно, просто поймите, что документы here, прикрепленные к входу функции, оцениваются на предмет расширения оболочки перед выполнением функции, и что они генерируются заново в том состоянии, в котором они были, когда функция была определена каждый раз, когда функция вызывается.
DECLARE
Вам просто нужна функция, которая объявляет другие функции.
_fn_init() { . /dev/fd/4 ; } 4<<INIT
${1}() { $(shift ; printf %s\\n "$@")
} 4<<-REQ 5<<-\\RESET
: \${_if_unset?shell will ERR and print this to stderr}
: \${common_param="REQ/RESET added to all funcs"}
REQ
_fn_init $(printf "'%s' " "$@")
RESET
INIT
ЗАПУСТИТЬ ЕГО
Здесь я призываю _fn_init
объявить мне функцию с именем fn
.
set -vx
_fn_init fn \
'echo "this would be command 1"' \
'echo "$common_param"'
#OUTPUT#
+ _fn_init fn 'echo "this would be command 1"' 'echo "$common_param"'
shift ; printf %s\\n "$@"
++ shift
++ printf '%s\n' 'echo "this would be command 1"' 'echo "$common_param"'
printf "'%s' " "$@"
++ printf ''\''%s'\'' ' fn 'echo "this would be command 1"' 'echo "$common_param"'
#ALL OF THE ABOVE OCCURS BEFORE _fn_init RUNS#
#FIRST AND ONLY COMMAND ACTUALLY IN FUNCTION BODY BELOW#
+ . /dev/fd/4
#fn AFTER _fn_init .dot SOURCES IT#
fn() { echo "this would be command 1"
echo "$common_param"
} 4<<-REQ 5<<-\RESET
: ${_if_unset?shell will ERR and print this to stderr}
: ${common_param="REQ/RESET added to all funcs"}
REQ
_fn_init 'fn' \
'echo "this would be command 1"' \
'echo "$common_param"'
RESET
ТРЕБУЕТСЯ
Если я хочу вызвать эту функцию, она умрет, если не установлена переменная окружения _if_unset
.
fn
#OUTPUT#
+ fn
/dev/fd/4: line 1: _if_unset: shell will ERR and print this to stderr
Обратите внимание на порядок трассировки оболочки - не только происходит fn
сбой при вызове, когда _if_unset
не установлен, но он никогда не запускается в первую очередь . Это самый важный фактор, который нужно понимать при работе с расширениями здесь-документа - они всегда должны появляться в первую очередь, потому что они есть в <<input
конце концов.
Ошибка возникает из- /dev/fd/4
за того, что родительская оболочка оценивает этот ввод перед передачей его функции. Это самый простой и самый эффективный способ проверки необходимой среды.
Во всяком случае, сбой легко исправить.
_if_unset=set fn
#OUTPUT#
+ _if_unset=set
+ fn
+ echo 'this would be command 1'
this would be command 1
+ echo 'REQ/RESET added to all funcs'
REQ/RESET added to all funcs
ГИБКИЙ
Переменная common_param
оценивается как значение по умолчанию на входе для каждой функции, объявленной _fn_init
. Но это значение также может быть изменено на любое другое, что также будет учитываться любой функцией, объявленной аналогичным образом. Я оставлю следы снарядов сейчас - мы не собираемся идти ни на какую неизведанную территорию или что-то еще.
set +vx
_fn_init 'fn' \
'echo "Hi! I am the first function."' \
'echo "$common_param"'
_fn_init 'fn2' \
'echo "This is another function."' \
'echo "$common_param"'
_if_unset=set ;
Выше я объявляю две функции и установить _if_unset
. Теперь, прежде чем вызывать любую функцию, я отключу ее, common_param
чтобы вы могли видеть, что они сами установят ее при вызове.
unset common_param ; echo
fn ; echo
fn2 ; echo
#OUTPUT#
Hi! I am the first function.
REQ/RESET added to all funcs
This is another function.
REQ/RESET added to all funcs
А теперь из области звонящего:
echo $common_param
#OUTPUT#
REQ/RESET added to all funcs
Но теперь я хочу, чтобы это было что-то совершенно другое:
common_param="Our common parameter is now something else entirely."
fn ; echo
fn2 ; echo
#OUTPUT#
Hi! I am the first function.
Our common parameter is now something else entirely.
This is another function.
Our common parameter is now something else entirely.
А если я сбросил _if_unset
?
unset _if_unset ; echo
echo "fn:"
fn ; echo
echo "fn2:"
fn2 ; echo
#OUTPUT#
fn:
dash: 1: _if_unset: shell will ERR and print this to stderr
fn2:
dash: 1: _if_unset: shell will ERR and print this to stderr
СБРОС
Если вам нужно сбросить состояние функции в любое время, это легко сделать. Вам нужно только сделать (изнутри функции):
. /dev/fd/5
Я сохранил аргументы, используемые для первоначального объявления функции во 5<<\RESET
входном файле-дескрипторе. Так .dot
что источник в оболочке в любое время повторит процесс, который его настроил в первую очередь. Это все довольно просто, на самом деле, и в значительной степени полностью переносимо, если вы не хотите учитывать тот факт, что POSIX фактически не указывает пути к узлам файлового дескриптора устройства (которые необходимы для оболочки .dot
).
Вы можете легко расширить это поведение и настроить различные состояния для вашей функции.
БОЛЬШЕ?
Кстати, это едва царапает поверхность. Я часто использую эти методы для встраивания небольших вспомогательных функций, объявляемых в любое время на вход основной функции - например, для дополнительных позиционных $@
массивов по мере необходимости. На самом деле - как я полагаю, это должно быть нечто очень близкое к тому, что делают оболочки высшего порядка в любом случае. Вы можете видеть, что они очень легко программно названы.
Я также хотел бы объявить функцию генератора, которая принимает ограниченный тип параметра, а затем определяет одноразовую или иначе ограниченную областью функцию горелки вдоль линий лямбда-функции или встроенную функцию, которая просто unset -f
сама по себе, когда через. Вы можете передать функцию оболочки вокруг.