Декоратор функции Bash

В python мы можем украсить функции кодом, который автоматически применяется и выполняется против функций.

Есть ли подобная функция в bash?

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

К сожалению, мне нужно повторно вставить этот код в каждую функцию, и если я хочу его изменить, мне придется модифицировать каждую функцию.

Есть ли способ удалить этот код из каждой функции и применить его ко всем функциям, подобным декораторам в python?

  • Развернуть переменную bash в команде pipeed
  • Терминальный эмулятор падает с функцией с помощью вложенных операторов case?
  • awk с параметром number для столбца, который вы хотите распечатать
  • Преобразование цикла кода в функцию
  • Как выполнять функции, созданные в сценарии bash, сохраняются, как в .bashrc?
  • Отслеживать определенные параметры для некоторой команды
  • Функция Bash с `getopts` работает только при первом запуске
  • Зашифрованный архив труб в загрузчик
  • 4 Solutions collect form web for “Декоратор функции Bash”

    Это было бы намного проще с zsh который имеет анонимные функции и специальный ассоциативный массив с кодами функций. Однако с помощью bash вы могли бы сделать что-то вроде:

     decorate() { eval " _inner_$(typeset -f "$1") $1"'() { echo >&2 "Calling function '"$1"' with $# arguments" _inner_'"$1"' "$@" local ret=$? echo >&2 "Function '"$1"' returned with exit status $ret" return "$ret" }' } f() { echo test return 12 } decorate f fab 

    Что будет выводить:

     Calling function f with 2 arguments test Function f returned with exit status 12 

    Вы не можете называть украшение дважды, чтобы украсить вашу функцию дважды.

    С zsh :

     decorate() functions[$1]=' echo >&2 "Calling function '$1' with $# arguments" () { '$functions[$1]'; } "$@" local ret=$? echo >&2 "function '$1' returned with status $ret" return $ret' 

    Я уже обсуждал хау и хау, о том, как несколько методов работают несколько раз, поэтому я больше не буду этого делать. Лично мои избранные на эту тему здесь и здесь .

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

    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 переменная среды _if_unset .

     fn #OUTPUT# + fn /dev/fd/4: line 1: _if_unset: shell will ERR and print this to stderr 

    Обратите внимание на порядок следов оболочки – не только fn сбой при вызове, когда _if_unset не установлен, но он никогда не запускается в первую очередь . Это самый важный фактор, который следует понимать при работе с расширениями здесь-документа – они всегда должны встречаться первыми, потому что они все- <<input являются « <<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 sourcing, что в оболочке в любое время будет повторять процесс, который его настроил в первую очередь. Все это довольно легко, и в значительной степени полностью переносимо, если вы хотите упускать из виду тот факт, что POSIX на самом деле не указывает пути узла устройства к файловому дескриптору (что является необходимостью для .dot оболочки).

    Вы можете легко расширить это поведение и настроить различные состояния для своей функции.

    БОЛЬШЕ?

    Между прочим, это едва царапает поверхность. Я часто использую эти методы для встраивания небольших вспомогательных функций, декларируемых в любое время во входную часть основной функции – например, для дополнительных позиционных $@ массивов по мере необходимости. На самом деле, как я считаю, это должно быть что-то очень близкое к этому, что ракеты высшего порядка все равно. Вы можете видеть, что их очень легко программно назвать.

    Я также хотел бы объявить функцию-генератор, которая принимает ограниченный тип параметра, а затем определяет одноразовую или иначе ограниченную по объему функцию записи по линиям лямбда – или встроенную функцию – это просто unset -f ' когда он проходит через. Вы можете передать функцию оболочки.

    Я думаю, что один из способов распечатать информацию о функции, когда вы

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

    заключается в изменении bash builtin return and / or exit в начале каждого скрипта (или в каком-либо файле, который вы запускаете каждый раз перед выполнением программы). Таким образом, вы вводите

      #!/bin/bash return () { if [ -z $1 ] ; then builtin return else if [ $1 -gt 0 ] ; then echo function ${FUNCNAME[1]} returns status $1 builtin return $1 else builtin return 0 fi fi } foo () { [ 1 != 2 ] && return 1 } foo 

    Если вы запустите это, вы получите:

      function foo returns status 1 

    Это может быть легко обновлено с помощью флага отладки, если вам нужно, примерно так:

      #!/bin/bash VERBOSE=1 return () { if [ -z $1 ] ; then builtin return else if [ $1 -gt 0 ] ; then [ ! -z $VERBOSE ] && [ $VERBOSE -gt 0 ] && echo function ${FUNCNAME[1]} returns status $1 builtin return $1 else builtin return 0 fi fi } 

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

    Аналогичным образом вы можете переопределить exit , заменив все экземпляры return , если вы хотите выйти из сценария.

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

     #!/bin/bash outer () { _ inner1 () { _ print "inner 1 command" } inner2 () { _ double_inner2 () { _ print "double_inner1 command" } double_inner2 print "inner 2 command" } inner1 inner2 inner1 print "just command in outer" } foo_with_args () { _ $@ print "command in foo with args" } echo command in body of script outer foo_with_args 

    И для вывода я могу получить это:

     command in body of script outer: inner1: inner 1 command inner2: double_inner2: double_inner1 command inner 2 command inner1: inner 1 command just command in outer foo_with_args: 1 2 3 command in foo with args 

    Это может быть полезно для тех, кто имеет функции и хочет их отладить, чтобы увидеть, в каких случаях произошла ошибка. Он основан на трех функциях, которые могут быть описаны ниже:

     #!/bin/bash set_indentation_for_print_function () { default_number_of_indentation_spaces="4" # number_of_spaces_of_current_function is set to (max number of inner function - 3) * default_number_of_indentation_spaces # -3 is because we dont consider main function in FUNCNAME array - which is if your run bash decoration from any script, # decoration_function "_" itself and set_indentation_for_print_function. number_of_spaces_of_current_function=`echo ${#FUNCNAME[@]} | awk \ -v default_number_of_indentation_spaces="$default_number_of_indentation_spaces" ' { print ($1-3)*default_number_of_indentation_spaces} '` # actual indent is sum of default_number_of_indentation_spaces + number_of_spaces_of_current_function let INDENT=$number_of_spaces_of_current_function+$default_number_of_indentation_spaces } print () { # print anything inside function with proper indent set_indentation_for_print_function awk -vl="${INDENT:=0}" 'BEGIN {for(i=1;i<=l;i++) printf(" ")}' # print INDENT spaces before echo echo $@ } _ () { # decorator itself, prints funcname: args set_indentation_for_print_function let INDENT=$INDENT-$default_number_of_indentation_spaces # we remove def_number here, because function has to be right from usual print awk -vl="${INDENT:=0}" 'BEGIN {for(i=1;i<=l;i++) printf(" ")}' # print INDENT spaces before echo #tput setaf 0 && tput bold # uncomment this for grey color of decorator [ $INDENT -ne 0 ] && echo "${FUNCNAME[1]}: $@" # here we avoid situation where decorator is used inside the body of script and not in the function #tput sgr0 # resets grey color } 

    Я попытался как можно больше добавить комментарии, но здесь также приводится описание: Я использую функцию _ () как декоратор, которую я положил после объявления каждой функции: foo () { _ . Эта функция печатает имя функции с соответствующим отступом, в зависимости от того, как глубокая функция находится в другой функции (в качестве отступа по умолчанию я использую 4 числа пробелов). Обычно я печатаю это серым цветом, чтобы отделить это от обычной печати. Если функция необходима для декорирования аргументами или без нее, можно изменить предварительную строку в функции декоратора.

    Чтобы напечатать что-то внутри функции, я ввел функцию print () которая печатает все, что передается ему с соответствующим отступом.

    Функция set_indentation_for_print_function делает именно то, что она обозначает, вычисляя отступ из ${FUNCNAME[@]} .

    Этот способ имеет некоторые недостатки, например, нельзя передавать параметры для print например, для echo , например -n или -e , а также если функция возвращает 1, она не декорирована. А также для аргументов, переданных для print больше, чем ширина терминала, которая будет завернута на экран, один не будет видеть отступ для завернутой строки.

    Отличный способ использования этих декораторов состоит в том, чтобы поместить их в отдельный файл и в каждый новый скрипт для источника этого файла source ~/script/hand_made_bash_functions.sh .

    Я думаю, что лучший способ включить декоратор функций в bash – это написать декоратор в теле каждой функции. Я думаю, что намного проще записывать функцию внутри функции в bash, потому что она имеет возможность установить все переменные глобальные, а не как стандартные объектно-ориентированные языки. Это делает так, будто вы помещаете метки вокруг своего кода в bash. По крайней мере, это помогло мне для отладочных сценариев.

    Возможно, примеры декораторов в проекте http://sourceforge.net/projects/oobash/ могут помочь вам (oobash / docs / examples / decorator.sh).

    Interesting Posts

    bash: выполнить команду в фоновом режиме и захватить pid

    Кто является пользователем -bash для пользователя на моем сервере?

    CentOS Формат USB без раздела

    Как создать файл с несколькими строками из командной строки?

    netcat6 в локальной сети IPv6

    Как Моно волшебный?

    Сценарий для замены всех символических ссылок на цель для указания на другую цель (в том же каталоге)

    Каков правильный способ ссылки на LINUX / UNIX в целом?

    Как ядро ​​знает, сколько адресной памяти пользователя нужно отложить?

    lsmod ничего не возвращает на моем встроенном устройстве

    Как отключить xxtrace и сохранить код выхода

    Попытка подключиться к экрану убивает терминал

    Когда оболочка запускает встроенную систему, а сигнал генерируется клавиатурой, что будет обрабатывать сигнал?

    как дать yes в качестве опции по умолчанию для apt-get install в ubuntu

    Как переключиться на Linux (Ubuntu) на Chromebook?

    Linux и Unix - лучшая ОС в мире.