Определите, существует ли функция в bash

В настоящее время я делаю некоторые модульные тесты, которые выполняются из Bash. Модульные тесты инициализируются, выполняются и очищаются в bash-скрипте. Этот скрипт обычно содержит функции init(), execute() и cleanup(). Но они не обязательны. Я хотел бы проверить, определены они или нет.

Раньше я делал это с помощью поиска и подмены источника, но это казалось неправильным. Есть ли более элегантный способ сделать это?

Изменение: следующий фрагмент работает как шарм:

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}

Ответ 1

Я думаю, что вы ищете команду типа. Он скажет вам, является ли что-то функцией, встроенной функцией, внешней командой или просто не определено. Пример:

$ LC_ALL=C type foo
bash: type: foo: not found

$ LC_ALL=C type ls
ls is aliased to 'ls --color=auto'

$ which type

$ LC_ALL=C type type
type is a shell builtin

$ LC_ALL=C type -t rvm
function

$ if [ -n "$(LC_ALL=C type -t rvm)" ] && [ "$(LC_ALL=C type -t rvm)" = function ]; then echo rvm is a function; else echo rvm is NOT a function; fi
rvm is a function

Ответ 2

$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1

Ответ 3

Если declare будет в 10 раз быстрее, чем тест, это может показаться очевидным.

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

#!/bin/sh

function_exists() {
    declare -f -F $1 > /dev/null
    return $?
}

function_exists function_name && echo Exists || echo No such function

Опция "-F" для объявления заставляет ее возвращать только имя найденной функции, а не все содержимое.

Невозможно измерить производительность при использовании /dev/null, и если это вас очень беспокоит:

fname=`declare -f -F $1`
[ -n "$fname" ]    && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist

Или объедините эти два, для вашего собственного бессмысленного наслаждения. Они оба работают.

fname=`declare -f -F $1`
errorlevel=$?
(( ! errorlevel )) && echo Errorlevel says $1 exists     || echo Errorlevel says $1 does not exist
[ -n "$fname" ]    && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist

Ответ 4

Заимствуя другие решения и комментарии, я придумал следующее:

fn_exists() {
  # appended double quote is an ugly trick to make sure we do get a string -- if $1 is not a known command, type does not output anything
  [ `type -t $1`"" == 'function' ]
}

Используется как...

if ! fn_exists $FN; then
    echo "Hey, $FN does not exist ! Duh."
    exit 2
fi

Он проверяет, является ли данный аргумент функцией, и избегает перенаправления и другого grepping.

Ответ 5

Дрессировка старого сообщения... но я недавно использовал это и испытал обе альтернативы, описанные с помощью:

test_declare () {
    a () { echo 'a' ;}

    declare -f a > /dev/null
}

test_type () {
    a () { echo 'a' ;}
    type a | grep -q 'is a function'
}

echo 'declare'
time for i in $(seq 1 1000); do test_declare; done
echo 'type'
time for i in $(seq 1 100); do test_type; done

это сгенерировано:

real    0m0.064s
user    0m0.040s
sys     0m0.020s
type

real    0m2.769s
user    0m1.620s
sys     0m1.130s

Объявление - это helluvalot быстрее!

Ответ 6

Это сводится к использованию 'declare' для проверки кода выхода или выхода.

Стиль вывода:

isFunction() { [[ "$(declare -Ff "$1")" ]]; }

Использование:

isFunction some_name && echo yes || echo no

Однако, если память обслуживается, перенаправление на нуль быстрее, чем замена вывода (говоря, ужасный и устаревший метод `cmd` должен быть изгнан и вместо него используется $(cmd).) И поскольку declare возвращает true/false если обнаружено/не найдено, а функции возвращают код завершения последней команды в функции, поэтому явный возврат обычно не требуется, и поскольку проверка кода ошибки выполняется быстрее, чем проверка строкового значения (даже пустая строка):

Стиль статуса выхода:

isFunction() { declare -Ff "$1" >/dev/null; }

Это, вероятно, примерно так же кратким и мягким, как вы можете получить.

Ответ 7

Тестирование различных решений:

#!/bin/bash

test_declare () {
    declare -f f > /dev/null
}

test_declare2 () {
    declare -F f > /dev/null
}

test_type () {
    type -t f | grep -q 'function'
}

test_type2 () {
     [[ $(type -t f) = function ]]
}

funcs=(test_declare test_declare2 test_type test_type2)

test () {
    for i in $(seq 1 1000); do $1; done
}

f () {
echo 'This is a test function.'
echo 'This has more than one command.'
return 0
}
post='(f is function)'

for j in 1 2 3; do

    for func in ${funcs[@]}; do
        echo $func $post
        time test $func
        echo exit code $?; echo
    done

    case $j in
    1)  unset -f f
        post='(f unset)'
        ;;
    2)  f='string'
        post='(f is string)'
        ;;
    esac
done

выводит, например:

test_declare (f is function)

реальный 0m0,055s пользователь 0m0,041s sys 0m0,004s код выхода 0

test_declare2 (f is function)

реальный 0m0,042s пользователь 0m0,022s sys 0m0,017s код выхода 0

test_type (f is function)

реальный 0m2,200s пользователь 0m1,619s sys 0m1,008s код выхода 0

test_type2 (f is function)

реальный 0m0,746s пользователь 0m0,534s sys 0m0,237s код выхода 0

test_declare (f unset)

реальный 0m0,040s пользователь 0m0,029s sys 0m0,010s код выхода 1

test_declare2 (f unset)

реальный 0m0,038s пользователь 0m0,038s sys 0m0,000s код выхода 1

test_type (f unset)

реальный 0m2,438s пользователь 0m1,678s sys 0m1,045s код выхода 1

test_type2 (f unset)

реальный 0m0,805s пользователь 0m0,541s sys 0m0,274s код выхода 1

test_declare (f is string)

реальный 0m0,043s пользователь 0m0,034s sys 0m0,007s код выхода 1

test_declare2 (f is string)

реальный 0m0,039s пользователь 0m0,035s sys 0m0,003s код выхода 1

test_type (f is string)

реальный 0m2,394s пользователь 0m1,679s sys 0m1,035s код выхода 1

test_type2 (f is string)

реальный 0m0,851s пользователь 0m0,554s sys 0m0,294s код выхода 1

Так что declare -F f кажется лучшим решением.

Ответ 8

fn_exists()
{
   [[ $(type -t $1) == function ]] && return 0
}

Обновление

isFunc () 
{ 
    [[ $(type -t $1) == function ]]
}

$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$

Ответ 9

Из моего комментария к другому ответу (который я не вижу, когда вернусь на эту страницу)

$ fn_exists() { test x$(type -t $1) = xfunction; }
$ fn_exists func1 && echo yes || echo no
no
$ func1() { echo hi from func1; }
$ func1
hi from func1
$ fn_exists func1 && echo yes || echo no
yes

Ответ 10

Это говорит вам, существует ли он, но не то, что это функция

fn_exists()
{
  type $1 >/dev/null 2>&1;
}

Ответ 11

Мне особенно понравилось решение от Grégory Joseph

Но я немного изменил его, чтобы преодолеть "двойную цитату уродливого трюка":

function is_executable()
{
    typeset TYPE_RESULT="`type -t $1`"

    if [ "$TYPE_RESULT" == 'function' ]; then
        return 0
    else
        return 1
    fi
}

Ответ 12

Я бы улучшил его, чтобы:

fn_exists()
{
    type $1 2>/dev/null | grep -q 'is a function'
}

И используйте его следующим образом:

fn_exists test_function
if [ $? -eq 0 ]; then
    echo 'Function exists!'
else
    echo 'Function does not exist...'
fi

Ответ 13

Можно использовать "тип" без каких-либо внешних команд, но вы должны называть его дважды, поэтому он все равно заканчивается примерно в два раза медленнее, чем версия "declare":

test_function () {
        ! type -f $1 >/dev/null 2>&1 && type -t $1 >/dev/null 2>&1
}

Плюс это не работает в POSIX sh, поэтому он абсолютно бесполезен, кроме как мелочи!