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

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

Я делал это ранее, используя greping и отправляя источник, но это казалось неправильным. Есть ли более элегантный способ сделать это?

Обновлено: следующий сниплет работает как шарм:

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

Спасибо. Я использовал это, чтобы условно определить заглушенные версии функций при загрузке библиотеки оболочки. fn_exists foo || foo() { :; }

Harvey 15.07.2013 14:02

Вы можете сохранить grep, используя type -t и ==.

Roland Weber 20.04.2018 11:41

Не работает, если языковой стандарт не английский. type test_function говорит test_function on funktio. при использовании финского языка и ist eine Funktion при использовании немецкого языка.

Kimmo Lehto 06.09.2018 09:55

Для неанглийских языков на помощь приходит LC_ALL=C

gaRex 16.02.2019 06:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
198
4
73 995
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Ответ принят как подходящий

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

$ 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

type -t $function - это талон на питание.

Allan Wind 17.09.2008 22:06

Почему вы не разместили это как ответ? :-)

terminus 20.09.2008 00:45

Потому что я опубликовал свой ответ, используя сначала объявление :-)

Allan Wind 20.09.2008 22:47

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

Lloeki 19.12.2013 12:46

Если я не прочитал неправильно, использование тип должно будет выполнить заведомо минимальный доступ, чтобы проверить, есть ли соответствующий файл. @Lloeki, вы совершенно правы, но это вариант, который дает минимальный результат, и вы все равно можете использовать уровень ошибок. Вы можете получить результат без подпроцесса, например type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile (плохой пример). Однако объявлять - лучший ответ, поскольку у него 0 disk io.

Orwellophile 20.12.2013 03:38

@AllanWind Вам определенно разрешено писать несколько ответов, хотя я полагаю, что еще в 2008 году это могло быть не так.

Steven Lu 22.02.2015 16:36

@Lloeki проверка возвращаемого значения по-прежнему работает нормально, поэтому я мог бы использовать type -t foo && echo "foo is something", я не вижу причин использовать вместо него declare, поскольку он все еще производит вывод, который, если нам нужно не показывать, мы должны перенаправить на / dev / null ...

Steven Lu 22.02.2015 16:39

@StevenLu что делать, если вам нужно знать, что foo - это конкретная функция? Разве это не возможно только с помощью declare (без использования подпроцесса или приведенного выше примера Орвеллофила)?

David Winiecki 16.04.2015 02:09

Я не знаю, кем я являюсь, но я знаю, чем я не являюсь, и это эксперт по оболочке ... при этом, похоже, declare - ваш лучший друг, если вы можете быть удовлетворены просто значением да / нет в качестве относительно того, является ли ваш символ функцией или нет, но я в основном сравнивал как синтаксис, так и вывод type и declare, и было действительно очевидно, что type -t намного более самодокументируется для сценариев оболочки общего назначения. Вы пишете сценарии оболочки. Subshell, чтобы облегчить понимание сценария? Да, черт возьми.

Steven Lu 16.04.2015 05:51

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

Steven Lu 16.04.2015 05:55

Также см. gnu.org/software/bash/manual/html_node/….

Big McLargeHuge 26.04.2019 18:57

Встроенная команда bash declare имеет параметр -F, который отображает все определенные имена функций. Если заданы аргументы имени, он покажет, какие из этих функций существуют, и, если все они это сделают, установит статус соответственно:

$ fn_exists() { declare -F "" > /dev/null; }

$ unset f
$ fn_exists f && echo yes || echo no
no

$ f() { return; }
$ fn_exist f && echo yes || echo no
yes

отлично поработал для меня. Тем более, что в моей оболочке нет флага -t для типа (у меня было много проблем с типом "$ command")

Dennis 07.04.2012 02:00

Действительно, он также работает в zsh (полезно для сценариев rc) и не требует grep для типа.

Lloeki 19.12.2013 12:39

@DennisHodapp нет необходимости в type -t, вместо этого вы можете полагаться на статус выхода. Давно пользовался type program_name > /dev/null 2>&1 && program_name arguments || echo "error", чтобы посмотреть, смогу ли я что-нибудь вызвать. Очевидно, type -t и вышеупомянутый метод также позволяют определять тип, а не только то, является ли он «вызываемым».

0xC0000022L 11.02.2015 23:08

@ 0xC0000022L что, если имя_программы не является функцией?

David Winiecki 16.04.2015 01:42

@DavidWiniecki: не уверен, о чем вы говорите?!? Я отвечал на другой комментарий. Если это не функция, ее все равно можно будет вызвать, и синтаксис будет таким же, будь то псевдоним, функция, встроенная или внешняя команда.

0xC0000022L 16.04.2015 21:11

@ 0xC0000022L Я придирался к тому, как использование статуса выхода не позволяет узнать, является ли имя_программы функцией, но теперь я думаю, что вы обратились к этому, когда сказали: «Очевидно, что тип -t и вышеуказанный метод также позволяют определять тип. , а не только то, является ли он «вызываемым». Извини.

David Winiecki 16.04.2015 22:06

Чтобы получить 1, если функция существует, и 0, если не существует: declare -f > /dev/null && echo 1 || echo 0;

VladSavitsky 15.05.2020 10:35

Какого черта? Любое описание? Обычно такие ответы на StackOverflow просто смиряются с -1.

F8ER 06.03.2021 08:54

Я добавил описание по запросу. Добейтесь голосов :-)

Allan Wind 06.03.2021 10:21

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

fn_exists()
{
    type  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

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

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

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

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

объявить шибко быстрее!

Это можно сделать без grep: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }

qneill 10.08.2015 16:21

@qneill Я провел несколько более обширный тест в мой ответ,

jarno 19.11.2016 16:04

ТРУБА - самый медленный элемент. Этот тест не сравнивает type и declare. Он сравнивает type | grep с declare. Это большая разница.

kyb 24.05.2018 14:22

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

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

К тому же это не работает в POSIX sh, так что ничего не стоит, кроме как пустяков!

test_type_nogrep () {а () {эхо 'а';}; local b = $ (тип a); c = $ {b // это функция /}; [$? = 0] && return 1 || возврат 0; } - qneill

Alexx Roche 23.06.2017 07:33

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

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

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

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

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

Приятно, мой фаворит из группы! Разве вам не нужны двойные кавычки вокруг аргумента? Как в [ $(type -t "")"" == 'function' ]

quickshiftin 26.11.2013 21:10

Спасибо @quickshiftin; Я не знаю, нужны ли мне эти двойные кавычки, но вы, вероятно, правы, хотя ... может ли функция даже быть объявлена ​​с именем, которое нужно было бы заключить в кавычки?

Grégory Joseph 06.12.2013 15:43

Вы используете bash, используйте [[...]] вместо [...] и избавьтесь от взлома цитат. Также делает вилку обратными кавычками, что работает медленно. Вместо этого используйте declare -f > /dev/null.

Lloeki 19.12.2013 12:43

Избегая ошибок с пустыми аргументами, сокращая кавычки и используя posix-совместимое равенство '=', его можно безопасно уменьшить до :: fn_exists() { [ x$(type -t ) = xfunction ]; }

qneill 10.08.2015 16:27

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

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

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

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

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

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

#!/bin/sh

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

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

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

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

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

Или скомбинируйте то и другое для собственного бессмысленного удовольствия. Они оба работают.

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

Параметр "-f" является избыточным.

Rajish 22.08.2013 14:03

Параметр -F не существует в zsh (полезно для переносимости)

Lloeki 19.12.2013 17:35

-F также не нужен: кажется, что он подавляет только определение / тело функции.

blueyed 21.09.2016 17:03

@blueyed Это может быть необязательно, но очень желательно, мы пытаемся подтвердить, что функция существует, а не перечислить все ее содержимое (что несколько неэффективно). Вы бы проверили наличие файла с помощью cat "$fn" | wc -c? Что касается zsh, если тег трепать вас не подсказал, возможно, сам вопрос должен быть. «Определить, существует ли функция в bash». Я также хотел бы указать, что, хотя параметр -F не существует в zsh, он также не вызывает ошибки, поэтому использование и -f, и -F позволяет успешной проверке как в zsh, так и в трепать, что в противном случае не было бы ' т.

Orwellophile 21.09.2016 18:24

@Orwellophile -F используется в zsh для чисел с плавающей запятой. Я не понимаю, почему использование -F делает его лучше в bash ?! У меня сложилось впечатление, что declare -f работает так же в bash (относительно кода возврата).

blueyed 23.09.2016 00:45

@blueyed: в трепать-F пропускает определение функции, а -f лишний, но безвредный. Правильный ответ был на самом деле declare -f "$fn". По неизвестным причинам я использовал оба флага. Позже было чистой случайностью обнаружить, что в zsh флаг -F не имеет смысла, но безвреден. Учитывая, что declare -fF "$fn" работает в обеих оболочках и не требует от меня двойной проверки аргументов, это мгновенный выигрыш для меня. И если вы не понимаете, почему -F предпочтительнее -f, то вы, вероятно, считаете cat "$filename" || fail эквивалентным. согласно test -f "$filename" || fail. Тот же принцип.

Orwellophile 23.09.2016 10:43

@Orwellophile У меня сложилось впечатление, что -F может быть вредным для zsh, но, видимо, это не так.

blueyed 24.09.2016 00:45

return $? является избыточным, удалите его, и все равно будет возвращен последний статус выхода.

FelipeC 29.12.2020 18:54

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

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

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

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

isFunction some_name && echo yes || echo no

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

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

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

Это, вероятно, настолько лаконично и мягко, насколько это возможно.

Для максимальной лаконичности используйте isFunction() { declare -F ""; } >&-.

Neil 29.05.2012 03:01

isFunction() { declare -F -- "$@" >/dev/null; } - моя рекомендация. Он также работает со списком имен (успешно, только если все являются функциями), не вызывает проблем с именами, начинающимися с -, и, на моей стороне (bash 4.2.25), declare всегда терпит неудачу, когда вывод закрывается с помощью >&-, потому что он не может записать имя в стандартный вывод в этом случае

Tino 13.05.2016 14:37

И имейте в виду, что на некоторых платформах echo может иногда давать сбой из-за «прерванного системного вызова». В этом случае "check && echo yes || echo no" все еще может выводить no, если check истинно.

Tino 13.05.2016 14:42

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

Обновить

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

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

Тестируем разные решения:

#!/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 ; 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)

real 0m0,055s user 0m0,041s sys 0m0,004s exit code 0

test_declare2 (f is function)

real 0m0,042s user 0m0,022s sys 0m0,017s exit code 0

test_type (f is function)

real 0m2,200s user 0m1,619s sys 0m1,008s exit code 0

test_type2 (f is function)

real 0m0,746s user 0m0,534s sys 0m0,237s exit code 0

test_declare (f unset)

real 0m0,040s user 0m0,029s sys 0m0,010s exit code 1

test_declare2 (f unset)

real 0m0,038s user 0m0,038s sys 0m0,000s exit code 1

test_type (f unset)

real 0m2,438s user 0m1,678s sys 0m1,045s exit code 1

test_type2 (f unset)

real 0m0,805s user 0m0,541s sys 0m0,274s exit code 1

test_declare (f is string)

real 0m0,043s user 0m0,034s sys 0m0,007s exit code 1

test_declare2 (f is string)

real 0m0,039s user 0m0,035s sys 0m0,003s exit code 1

test_type (f is string)

real 0m2,394s user 0m1,679s sys 0m1,035s exit code 1

test_type2 (f is string)

real 0m0,851s user 0m0,554s sys 0m0,294s exit code 1

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

Внимание: declare -F f не возвращает ненулевое значение, если f не существует на zsh, но bash да. Будьте осторожны при его использовании. declare -f f, с другой стороны, работает так, как ожидалось, добавляя определение функции к stdout (что может раздражать ...)

Manoel Vilela 30.10.2017 12:43

Вы пробовали test_type3 () { [[ $(type -t f) = function ]] ; }, есть предельная стоимость определения локальной переменной (хотя и <10%)

Oliver 03.02.2019 17:22

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

$ fn_exists() { test x$(type -t ) = 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

Вызов функции, если она определена.

Известное имя функции. Скажем, имя my_function, затем используйте

[[ "$(type -t my_function)" == 'function' ]] && my_function;
# or
[[ "$(declare -fF my_function)" ]] && my_function;

Имя функции в переменной. Если мы объявим func=my_function, мы сможем использовать

[[ "$(type -t $func)" == 'function' ]] && $func;
# or
[[ "$(declare -fF $func)" ]] && $func;

Логическая инверсия с тем же результатом. Все сразу. Здесь мы используем || вместо &&.

[[ "$(type -t my_function)" != 'function' ]] || my_function;
[[ ! "$(declare -fF my_function)" ]] || my_function;
func=my_function
[[ "$(type -t $func)" != 'function' ]] || $func;
[[ ! "$(declare -fF $func)" ]] || $func;

Опасная ситуация при использовании || с return внутри функции
Следующая комбинация заставит вас завершить процесс оболочки.

# Some script execution strict mode. The essence is in the "-e"
set -euf +x -o pipefail

function run_if_exists(){
    my_function=

    [[ "$(type -t my_function)" != 'function' ]] || return;

    $my_function
}

run_if_exists  non_existing_function

Это потому, что приведенное выше эквивалентно

set -e
function run_if_exists(){
    return 1;
}
run_if_exists

который всегда будет терпеть неудачу. Чтобы предотвратить это, вы должны использовать || { true; return; } или что-то еще в таком предусловии вашей функции.

    [[ "$(type -t my_function)" != 'function' ]] || { true; return; }

Другие вопросы по теме