Как переименовать функцию bash? - PullRequest
28 голосов
/ 30 июля 2009

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

Чтобы привести краткий пример наивной попытки, которую я не ожидал (а на самом деле это не так):

$ theirfunc() { echo "do their thing"; }
$ _orig_theirfunc() { theirfunc; }
$ theirfunc() { echo "do my thing"; _orig_theirfunc }
$ theirfunc
do my thing
do my thing
do my thing
...

Очевидно, я не хочу бесконечной рекурсии, я хочу:

do my thing
do their thing

Как я могу это сделать?

Ответы [ 8 ]

38 голосов
/ 02 сентября 2009

Вот способ удалить временный файл:

$ theirfunc() { echo "do their thing"; }
$ eval "$(echo "orig_theirfunc()"; declare -f theirfunc | tail -n +2)"
$ theirfunc() { echo "do my thing"; orig_theirfunc; }
$ theirfunc
do my thing
do their thing
13 голосов
/ 30 июля 2009

Aha. Нашел решение, хотя оно не очень красивое:

$ theirfunc() { echo "do their thing"; }
$ echo "orig_theirfunc()" > tmpfile
$ declare -f theirfunc | tail -n +2 >> tmpfile
$ source tmpfile
$ theirfunc() { echo "do my thing"; orig_theirfunc; }
$ theirfunc
do my thing
do their thing

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

Обновление : мастер bash Эван Бродер принял вызов (см. Принятый ответ выше). Я переформулировал его ответ в общую функцию «copy_function»:

# copies function named $1 to name $2
copy_function() {
    declare -F $1 > /dev/null || return 1
    eval "$(echo "${2}()"; declare -f ${1} | tail -n +2)"
}

Можно использовать так:

$ theirfunc() { echo "do their thing"; }
$ copy_function theirfunc orig_theirfunc
$ theirfunc() { echo "do my thing"; orig_theirfunc; }
$ theirfunc
do my thing
do their thing

Очень приятно!

12 голосов
/ 17 сентября 2013

Дальнейшее использование функций copy_function и rename_function для:

copy_function() {
  test -n "$(declare -f "$1")" || return 
  eval "${_/$1/$2}"
}

rename_function() {
  copy_function "$@" || return
  unset -f "$1"
}

Начиная с решения @Dmitri Rubinstein:

  • Не нужно звонить declare дважды. Проверка ошибок все еще работает.
  • Устранить временную переменную (func) с помощью специальной переменной _.
    • Примечание: использование test -n ... было единственным способом, которым я смог придумать, чтобы сохранить _ и все еще иметь возможность вернуться при ошибке.
  • Изменить return 1 на return (который возвращает текущий код состояния)
  • Используйте замену шаблона вместо удаления префикса.

После определения copy_function значение rename_function становится тривиальным. (Только не переименовывайте copy_function; -)

7 голосов
/ 06 сентября 2015

Если вы просто хотите добавить что-то к имени, скажем orig_, то я думаю, что самое простое это

eval orig_"$(declare -f theirfun)"
5 голосов
/ 27 июля 2012

Функцию copy_ можно улучшить, используя расширение параметра оболочки вместо команды tail:

copy_function() {
  declare -F "$1" > /dev/null || return 1
  local func="$(declare -f "$1")"
  eval "${2}(${func#*\(}"
}
3 голосов
/ 09 декабря 2015

Чтобы суммировать все другие решения и частично исправить их, вот решение, которое:

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

Но:

  • Вероятно, он не работает с рекурсивными функциями, поскольку имя функции, используемое для рекурсии внутри копии, не заменяется. Правильно подобрать замену - слишком сложная задача. Если вы хотите использовать такие замены, вы можете попробовать этот ответ https://stackoverflow.com/a/18839557 с eval "${_//$1/$2}" вместо eval "${_/$1/$2}" (обратите внимание на двойной //). Однако замена имени завершается неудачно для слишком простых имен функций (например, a) и не может быть выполнена для вычисляемой рекурсии (например, command_help() { case "$1" in ''|-help) echo "help [command]"; return;; esac; "command_$1" -help "${@:2}"; })

Все вместе:

: rename_fn oldname newname
rename_fn()
{
  local a
  a="$(declare -f "$1")" &&
  eval "function $2 ${a#*"()"}" &&
  unset -f "$1";
}

сейчас тесты:

somefn() { echo one; }
rename_fn somefn thatfn
somefn() { echo two; }
somefn
thatfn

выводит по мере необходимости:

two
one

Теперь попробуйте несколько более сложных случаев, которые все дают ожидаемые результаты или неудачи:

rename_fn unknown "a b"; echo $?
rename_fn "a b" murx; echo $?

a(){ echo HW; }; rename_fn " a " b; echo $?; a
a(){ echo "'HW'"; }; rename_fn a b; echo $?; b
a(){ echo '"HW"'; }; rename_fn a b; echo $?; b
a(){ echo '"HW"'; }; rename_fn a "b c"; echo $?; a

Можно утверждать, что следующее по-прежнему ошибка:

a(){ echo HW; }; rename_fn a " b "; echo $?; b

, поскольку это должно произойти сбой, поскольку " b " не является правильным именем функции Если вы действительно этого хотите, вам нужен следующий вариант:

rename_fn()
{
  local a
  a="$(declare -f "$1")" &&
  eval "function $(printf %q "$2") ${a#*"()"}" &&
  unset -f "$1";
}

Теперь это ловит и этот искусственный случай. (Обратите внимание, что printf с %q является bash встроенным.)

Конечно, вы можете разделить это на копии + переименовать так:

copy_fn() { local a; a="$(declare -f "$1")" && eval "function $(printf %q "$2") ${a#*"()"}"; }
rename_fn() { copy_fn "$@" && unset -f "$1"; }

Надеюсь, это 101% решение. Если это нуждается в улучшении, пожалуйста, прокомментируйте;)

2 голосов
/ 21 мая 2012

Вот функция, основанная на подходе Эвана Бродера:

# Syntax: rename_function <old_name> <new_name>
function rename_function()
{
    local old_name=$1
    local new_name=$2
    eval "$(echo "${new_name}()"; declare -f ${old_name} | tail -n +2)"
    unset -f ${old_name}
}

Как только это определено, вы можете просто сделать rename_function func orig_func

Обратите внимание, что вы можете использовать связанный подход для украшения / изменения / переноса существующих функций, как в ответе @ phs:

# Syntax: prepend_to_function <name> [statements...]
function prepend_to_function()
{
    local name=$1
    shift
    local body="$@"
    eval "$(echo "${name}(){"; echo ${body}; declare -f ${name} | tail -n +3)"
}

# Syntax: append_to_function <name> [statements...]
function append_to_function()
{
    local name=$1
    shift
    local body="$@"
    eval "$(declare -f ${name} | head -n -1; echo ${body}; echo '}')"
}

Как только они будут определены, предположим, что у вас есть существующая функция следующим образом:

function foo()
{
    echo stuff
}

Тогда вы можете сделать:

prepend_to_function foo echo before
append_to_function foo echo after

Используя declare -f foo, мы можем увидеть эффект:

foo ()
{
    echo before;
    echo stuff;
    echo after
}
1 голос
/ 28 февраля 2018

Для тех из нас, кто вынужден быть совместимым с bash 3.2 (вы знаете, о ком мы говорим), declare -f не работает. Я нашел type может работать

eval "$(type my_func | sed $'1d;2c\\\nmy_func_copy()\n')"

В форме функции это будет выглядеть как

copy_function()
{
  eval "$(type "${1}"| sed $'1d;2c\\\n'"${2}"$'()\n')"
}

И если вы действительно не хотите полагаться на sed ...

function copy_function()
{
  eval "$({
  IFS='' read -r line
  IFS='' read -r line
  echo "${2} ()"
  while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
  done
  }< <(type "${1}"))"
}

Но это немного словесно для меня

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...