确定bash中是否存在函数

确定bash中是否存在函数

Determine if a function exists in bash

目前,我正在做一些从bash执行的单元测试。 单元测试是在bash脚本中初始化,执行和清理的。 该脚本通常包含init(),execute()和cleanup()函数。 但是它们不是强制性的。 我想测试它们是否定义。

之前,我是通过摸索和诱骗来源来做到这一点的,但这似乎是错误的。 有没有更优雅的方法可以做到这一点?

编辑:以下代码段就像一个超级按钮一样工作:

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

我认为您正在寻找"类型"命令。它会告诉您某些东西是函数,内置函数,外部命令还是只是未定义。例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ 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

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


如果声明比测试快10倍,这似乎是显而易见的答案。

编辑:在下面,-f选项与BASH无关,请随时将其忽略。就个人而言,我很难记住哪个选项可以执行哪个操作,因此我只能同时使用两者。 -f显示功能,-F显示功能名称。

1
2
3
4
5
6
7
8
#!/bin/sh

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

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

声明的" -F"选项使它仅返回找到的函数的名称,而不返回整个内容。

使用/ dev / null不应对性能造成任何可衡量的影响,如果它让您担心的那么多:

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

或将两者结合起来,为自己带来无意义的享受。他们俩都工作。

1
2
3
4
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

借用其他解决方案和评论,我想到了这一点:

1
2
3
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' ]
}

用作...

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

它检查给定的参数是否是一个函数,并避免重定向和其他grepping。


疏通旧帖子...但是我最近使用了它,并测试了以下描述的两种替代方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

这产生了:

1
2
3
4
5
6
7
8
real    0m0.064s
user    0m0.040s
sys     0m0.020s
type

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

声明是helluvalot更快!


测试不同的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/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)

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似乎是最好的解决方案。


归结为使用"声明"来检查输出或退出代码。

输出样式:

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

用法:

1
isFunction some_name && echo yes || echo no

但是,如果使用内存,则重定向到null的速度比输出替换的速度要快(可以说,应该废除过时且过时的cmd方法,而应使用$(cmd)。)而且,如果找到find,则声明返回true / false。未找到,并且函数返回函数中最后一个命令的退出代码,因此通常不需要显式返回,并且由于检查错误代码比检查字符串值(甚至是空字符串)要快:

退出状态样式:

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

您可能会得到尽可能简洁和良性的评价。


从我对另一个答案的评论中(当我回到此页面时,我一直想念它)

1
2
3
4
5
6
7
8
$ 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

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

更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
isFunc ()
{
    [[ $(type -t $1) == function ]]
}

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

我特别喜欢格雷戈里·约瑟夫的解决方案

但是我已经对其进行了一些修改,以克服"双引号丑陋的把戏":

1
2
3
4
5
6
7
8
9
10
function is_executable()
{
    typeset TYPE_RESULT="`type -t $1`"

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

这告诉您它是否存在,但不是它是一个函数

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

我将其改进为:

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

并像这样使用它:

1
2
3
4
5
6
fn_exists test_function
if [ $? -eq 0 ]; then
    echo 'Function exists!'
else
    echo 'Function does not exist...'
fi

可以使用'type'而不使用任何外部命令,但是您必须调用它两次,因此它的运行速度仍然是'declare'版本的两倍:

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

另外,这在POSIX sh中不起作用,因此,除了琐事之外,它毫无价值!


推荐阅读