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

187

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

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

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

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}
конечная станция
источник
Спасибо. Я использовал это для условного определения заглушенных версий функций при загрузке библиотеки оболочки. fn_exists foo || foo() { :; }
Харви
2
Вы можете сохранить grep с помощью type -tи ==.
Роланд Вебер
Не работает, когда локаль не английская. type test_functionговорит test_function on funktio.при использовании финского языка и ist eine Funktionпри использовании немецкого языка.
Киммо Лехто
3
Для неанглийских локалей LC_ALL=Cв
реске

Ответы:

192

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

$ 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
JBB
источник
120
type -t $functionэто билет на еду.
Аллан Уинд
4
Почему вы не опубликовали это как ответ? :-)
конец
Потому что я опубликовал свой ответ, используя сначала объявить :-)
Аллан Уинд
5
type [-t]приятно рассказать вам, что это такое, но при тестировании, если что-то является функцией, это происходит медленно, так как вам приходится передавать по конвейеру grep или использовать обратные пометки, которые порождают подпроцесс.
Lloeki
1
Если я не прочитал неправильно, использование type должно будет выполнять, по общему признанию, минимальный доступ, чтобы проверить, есть ли соответствующий файл. @Lloeki, вы совершенно правы, но эта опция дает минимальный вывод, и вы все равно можете использовать уровень ошибки. Вы можете получить результат без подпроцесса, например type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(плохой пример). Однако, объявлять является лучшим ответом, так как он имеет 0 дисков io.
Орвеллофил
79
$ g() { return; }
$ declare -f g > /dev/null; echo $?
0
$ declare -f j > /dev/null; echo $?
1
Аллан Уинд
источник
1
работал потрясающе для меня. Тем более, что моя оболочка не имеет флага -t для типа (у меня было много проблем с типом "$ command")
Деннис
2
Действительно, он также работает в zsh (полезно для сценариев rc) и не требует grep для типа.
Lloeki
2
@ DennisHodapp не нужно type -t, вы можете положиться на статус выхода вместо этого. Я давно привык type program_name > /dev/null 2>&1 && program_name arguments || echo "error"видеть, смогу ли я позвонить. Очевидно, type -tчто метод выше и выше также позволяет определять тип, а не только то, является ли он «вызываемым».
0xC0000022L
@ 0xC0000022L что если имя_программы не является функцией?
Дэвид Винецки
2
@ 0xC0000022L Я придираюсь к тому, что использование состояния выхода не позволяет узнать, является ли имя_программы функцией, но теперь я думаю, что вы обратились к ней, когда сказали: «Очевидно, тип -t и описанный выше метод также позволяют определять тип не просто "вызываемый". " Сожалею.
Дэвид Винецкий
40

Если объявить в 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
Orwellophile
источник
2
Опция '-f' является избыточной.
Раджиш
3
-FВариант дез не существует в Zsh (полезно для переносимости)
Lloeki
-Fна самом деле также не является необходимым: кажется, он подавляет только определение функции / тело.
голубоглазый
1
@blueyed Это может быть необязательно, но это очень желательно, мы пытаемся подтвердить, что функция существует, а не перечислять все ее содержимое (что несколько неэффективно). Вы бы проверили наличие файла cat "$fn" | wc -c? Что касается zsh, то, если тег bash не подскажет вам, возможно, сам вопрос должен возникнуть. Msgstr "Определить, существует ли функция в bash". Я также хотел бы отметить, что, хотя -Fопция не существует в zsh, она также не вызывает ошибку, поэтому использование как -f, так и -F позволяет выполнить проверку как в zsh, так и в bash, что в противном случае не будет ,
Орвеллофил
@Orwellophile -Fиспользуется в zsh для чисел с плавающей запятой. Я не могу понять, почему использование -Fделает его лучше в Bash ?! У меня сложилось впечатление, что declare -fв bash работает то же самое (относительно кода возврата).
голубоглазый
18

Заимствуя из других решений и комментариев, я придумал это:

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

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

Грегори Джозеф
источник
Здорово, мой любимчик из группы! Разве вы не хотите двойные кавычки вокруг аргумента? Как в[ $(type -t "$1")"" == 'function' ]
quickshiftin
Спасибо @quickshiftin; Я не знаю, хочу ли я эти двойные кавычки, но вы, вероятно, правы, хотя ... может ли функция быть объявлена ​​с именем, которое нужно заключить в кавычки?
Грегори Жозеф
4
Вы используете Bash, используйте [[...]]вместо [...]и избавиться от взлома цитаты. Также разветвление backticks, которое является медленным. Используйте declare -f $1 > /dev/nullвместо этого.
Lloeki
3
Избегая ошибок с пустыми аргументами, сокращая кавычки и используя равенство posix '=', его можно безопасно уменьшить до :: fn_exists() { [ x$(type -t $1) = xfunction ]; }
qneill
10

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

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

объявляю это чертовски быстрее!

jonathanserafini
источник
1
Это можно сделать без grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill
@qneill Я сделал несколько более подробный тест в своем ответе ,
Ярно
ТРУБКА - самый медленный элемент. Этот тест не сравнивает typeи declare. Это сравнивается type | grepс declare. Это большая разница.
Кай
7

Это сводится к тому, чтобы использовать 'Declare' для проверки кода выхода или выхода.

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

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

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

isFunction some_name && echo yes || echo no

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

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

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

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

Скотт
источник
3
Для максимальной краткости используйтеisFunction() { declare -F "$1"; } >&-
Нейл
3
isFunction() { declare -F -- "$@" >/dev/null; }моя рекомендация Он также работает со списком имен (успешно, только если все являются функциями), не создает проблем с именами, начинающимися с, -и, с моей стороны ( bash4.2.25), declareвсегда завершается ошибкой при закрытии вывода >&-, поскольку не может записать имя в этом случае
Тино
И имейте в виду, что echoиногда может произойти сбой с «прерванным системным вызовом» на некоторых платформах. В этом случае «проверить && эхо да || эхо нет» по- прежнему может выводить , noесли checkэто правда.
Тино
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 является функцией)

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

test_declare2 (f является функцией)

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

test_type (f является функцией)

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

test_type2 (f является функцией)

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

test_declare (если не установлено)

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

test_declare2 (найдено не установлено)

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

test_type (f не установлен)

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

test_type2 (не установлено)

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

test_declare (f это строка)

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

test_declare2 (f - строка)

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

test_type (f это строка)

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

test_type2 (f это строка)

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

Так что, declare -F fпохоже, лучшее решение.

Ярно
источник
Внимание: declare -F fне возвращает ненулевое значение, если f не существует в zsh, но bash yes. Будьте осторожны, используя его. declare -f fс другой стороны, работает как положено, добавляя определение функции на стандартный вывод (что может раздражать ...)
Маноэль Вилела
1
Вы пробовали test_type3 () { [[ $(type -t f) = function ]] ; }есть предельные затраты на определение локальной переменной (хотя <10%)
Оливер
4

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

$ 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
qneill
источник
3
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
$
Иона
источник
2

Я бы улучшил это до:

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

источник
2

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

fn_exists()
{
  type $1 >/dev/null 2>&1;
}
Джейсон Планк
источник
2

Мне особенно понравилось решение от Грегори Жозефа

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

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

    if [ "$TYPE_RESULT" == 'function' ]; then
        return 0
    else
        return 1
    fi
}
b1r3k
источник
0

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

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

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

Ноа Спурриер
источник
test_type_nogrep () {a () {echo 'a';}; местный b = $ (тип а); c = $ {b // является функцией /}; [$? = 0] && return 1 || вернуть 0; } - Qneill
Alexx Roche