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

Я на самом деле использую openSUSE, но @F.Hauri является в основном правильным.

Файлы, которые были на самом деле полностью удалены (от мусора) не зарегистрированы. Я предполагаю, что Вы выполняете стандартную установку, таким образом, файловая система должна быть чем-то как ext3 или ext4. Существует много инструментов, которые могут просканировать Вашу файловую систему для остатков удаленных файлов, например, http://extundelete.sourceforge.net/, Это найдет много файлов, даже очень старого материала, в случае, если они не были перезаписаны другими файлами.

10
21.04.2014, 16:47
6 ответов

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

0
27.01.2020, 20:00

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

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

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

RESET

Если вам нужно сбросить состояние функции в любой момент легко.Вам нужно только сделать (изнутри функции):

. /dev/fd/5

Я сохранил аргументы, использованные для первоначального объявления функции, во входном файловом дескрипторе 5 << \ RESET . Таким образом, поиск .dot , который находится в оболочке, в любой момент повторит процесс, который изначально установил его. На самом деле все довольно просто и практически полностью переносимо, если вы хотите упустить из виду тот факт, что POSIX на самом деле не указывает пути к узлам файлового дескриптора устройства (которые необходимы для .dot оболочки ]).

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

БОЛЬШЕ?

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

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

5
27.01.2020, 20:00

Это было бы намного проще с 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
f a b

Что выведет:

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

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

С помощью 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'
12
27.01.2020, 20:00

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

проверяете требуемые аргументы и выходите, если они не существуют - и отображаете некоторые messages

предназначены для изменения встроенной команды bash return и / или 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 , если вы хотите выйти из сценария.

РЕДАКТИРОВАТЬ: Я хотел добавить сюда способ декорирования функций в 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 -v l="${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 -v l="${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. По крайней мере, это помогло мне в отладке скриптов.

2
27.01.2020, 20:00

Мне кажется, это самый простой способ реализовать шаблон декоратора внутри bash.

#!/bin/bash

function decorator {
    if [ "${FUNCNAME[0]}" != "${FUNCNAME[2]}" ] ; then
        echo "Turn stuff on"
        #shellcheck disable=2068
        ${@}
        echo "Turn stuff off"
        return 0
    fi
    return 1
}

function highly_decorated {
    echo 'Inside highly decorated, calling decorator function'
    decorator "${FUNCNAME[0]}" "${@}" && return
    echo 'Done calling decorator, do other stuff'
    echo 'other stuff'
}

echo 'Running highly decorated'
# shellcheck disable=SC2119
highly_decorated
0
20.08.2021, 12:53

Я много занимаюсь (возможно, даже слишком:))метапрограммированием в Bash, и обнаружил бесценные декораторы для повторной -реализации поведения на лету. Моя библиотека bash -cache использует оформление для прозрачного запоминания функций Bash с минимальными церемониями:

my_expensive_function() {
 ...
} && bc::cache my_expensive_function

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

# Given a name and an existing function, create a new function called name that
# executes the same commands as the initial function.
bc::copy_function() {
  local function="${1:?Missing function}"
  local new_name="${2:?Missing new function name}"
  declare -F "$function" &> /dev/null || {
    echo "No such function ${function}" >&2; return 1
  }
  eval "$(printf "%s()" "$new_name"; declare -f "$function" | tail -n +2)"
}

Вот простой пример декоратора, который timeявляется декорированной функцией, используяbc::copy_function:

time_decorator() {
  bc::copy_function "$1" "time_dec::${1}" || return
  eval "${1}() { time time_dec::${1} "'"\$@"; }'
}

Демо:

$ slow() { sleep 2; echo done; }

$ time_decorator slow

$ $ slow
done

real    0m2.003s
user    0m0.000s
sys     0m0.002s
0
20.08.2021, 12:53

Теги

Похожие вопросы