Визначте, чи існує функція в bash


187

В даний час я роблю одиничні тести, які виконуються з bash. Блокові тести ініціалізуються, виконуються та очищаються за допомогою bash-сценарію. Цей сценарій зазвичай містить функції init (), Execute () та очищення (). Але вони не є обов'язковими. Я хотів би перевірити, чи вони не визначені.

Я робив це раніше, зігріваючи та відкладаючи джерело, але це здавалося неправильним. Чи є більш елегантний спосіб це зробити?

Редагувати: Наступний фрагмент працює як шарм:

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

Дякую. Я використовував це для умовного визначення стертих версій функцій під час завантаження бібліотеки оболонок. fn_exists foo || foo() { :; }
Харві

2
Ви можете зберегти греп за допомогою type -tі ==.
Роланд Вебер

Не працює, коли мова не є англійською. type test_functionговорить test_function on funktio.при використанні фінської мови та ist eine Funktionпри використанні німецької мови.
Кіммо Лехто

3
Для неанглійських районів LC_ALL=Cв Resque
gaRex

Відповіді:


191

Я думаю, ти шукаєш команду 'type'. Він розповість, чи є щось функцією, вбудованою функцією, зовнішньою командою чи просто не визначено. Приклад:

$ 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

120
type -t $function- їжа на їжу.
Аллан Вітер

4
Чому ви не опублікували це як відповідь? :-)
термін

Тому що я опублікував свою відповідь, використовуючи заявити спочатку :-)
Аллан Вітер,

5
type [-t]приємно сказати вам, що є річ, але при тестуванні, чи щось є функцією, це повільно, оскільки вам доведеться перекочувати або використовувати зворотні посилання, обидва з яких створюють підпроцес.
Льоекі

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

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

1
спрацювало для мене приголомшливо. Тим більше, що в моїй оболонці немає прапора -t для типу (у мене були проблеми з типом "$ command")
Денніс

2
Дійсно, він також працює в zsh (корисно для rc-скриптів), і не вимагає grep для типу.
Льоекі

2
@DennisHodapp не потрібно type -t, ви можете покластися на статус виходу. Я давно звик type program_name > /dev/null 2>&1 && program_name arguments || echo "error"бачити, чи зможу я щось зателефонувати. Очевидно, що type -tі вищевказаний метод також дозволяє виявити тип, а не лише те, чи він "може дзвонити".
0xC0000022L

@ 0xC0000022L що робити, якщо ім'я_файлу не є функцією?
David Winiecki

2
@ 0xC0000022L Мені було зрозуміло, як використання статусу виходу не дає вам знати, чи є ім'ям програми_назва, але тепер, я думаю, ви звернулися до цього, коли ви сказали "Очевидно, що тип -t і вищевказаний метод також дозволяє виявити тип , а не лише те, чи "дзвонить". " Вибачте.
David Winiecki

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

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робить це краще в баш ?! У мене склалося враження, що так само 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; Я не знаю, чи хочу я цих подвійних лапок, але ви, мабуть, праві, хоча .. може функція навіть оголошена з ім'ям, яке потрібно було б цитувати?
Григорій Йосиф

4
Ви використовуєте bash, використовуйте [[...]]замість цього [...]і позбудьтеся цитата. Також задні вилки, які повільно. Використовуйте declare -f $1 > /dev/nullзамість цього.
Льоекі

3
Уникаючи помилок із порожніми аргументами, зменшення лапок та використання рівності, сумісного з позицією '=', це можна сміливо звести до :: 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

заявити, що це пекло швидше!


1
Це можна зробити без test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
грепа

@qneill Я зробив дещо більш масштабний тест у своїй відповіді ,
jarno

PIPE - найповільніший елемент. Цей тест не порівнює typeі declare. Він порівнює type | grepз declare. Це велика різниця.
киб

7

Це зводиться до використання "оголосити" для перевірки вихідного або вихідного коду.

Стиль виводу:

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

Використання:

isFunction some_name && echo yes || echo no

Однак якщо пам'ять служить, перенаправлення на null відбувається швидше, ніж підміна виводу (якщо говорити про, жахливий і застарілий метод `cmd 'слід відмовити, а замість цього використовувати $ (cmd). не знайдено, і функції повертають код виходу останньої команди у функції, тому явного повернення зазвичай не потрібно, а оскільки перевірка коду помилки швидше, ніж перевірка значення рядка (навіть нульового рядка):

Вихід із стилю статусу:

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

Мабуть, це настільки ж лаконічно і доброзичливо, як ви можете отримати.


3
Для максимального використання лаконічностіisFunction() { declare -F "$1"; } >&-
Ніл

3
isFunction() { declare -F -- "$@" >/dev/null; }моя рекомендація. Він також працює у списку імен (досягає успіху лише у тому випадку, якщо всі вони є функціями), не створює проблем із іменами, починаючи з -і, з моєї сторони ( bash4.2.25), declareзавжди виходить з ладу, коли вихід закритий >&-, оскільки він не може записати ім'я щоб виступити в цьому випадку
Тіно

І майте на увазі, що echoіноді може не вдатися до "перерваного системного дзвінка" на деяких платформах. У такому випадку "check && echo yes || echo no" все ще може вивести, 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 (f не встановлено)

реальний користувач 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 знято)

реальний користувач 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 так. Будьте обережні, використовуючи його. declare -f fз іншого боку, працює, як очікувалося, додаючи визначення функції на stdout (що може дратувати ...)
Manoel Vilela

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

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

Особливо мені сподобалось рішення Грегорі Йосифа

Але я трохи змінив це, щоб подолати "потворну хитрість подвійної цитати":

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

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

0

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

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 = $ (тип a); c = $ {b // є функцією /}; [$? = 0] && return 1 || повернути 0; } - qneill
Alexx Рош
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.