确定bash中是否存在函数


187

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

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

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

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

谢谢。在加载外壳程序库时,我用它来有条件地定义函数的存根版本。fn_exists foo || foo() { :; }
哈维

2
您可以使用type -t和保存grep ==
罗兰·韦伯

语言环境为非英语时不起作用。type test_functiontest_function on funktio.用芬兰语语言环境和时ist eine Funktion采用德国时。
Kimmo Lehto

3
对于非英语语言环境LC_ALL=C的resque
gaRex

Answers:


191

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

$ 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

119
type -t $function饭票。
艾伦·

4
您为什么不将其发布为答案?:-)
总站

因为我已经使用声明首先发布了答案:-)
艾伦·

5
type [-t]很高兴告诉您什么是事物,但是在测试某事物是否为函数时,它很慢,因为您必须通过管道传递给grep或使用反引号,这两者都会产生一个子进程。
Lloeki 2013年

1
除非我读错了,否则使用type必须执行公认的最小访问,以检查是否存在匹配的文件。@Lloeki,您是完全正确的,但这是产生最小输出的选项,您仍然可以使用错误级别。您可以在没有子流程的情况下获得结果,例如type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile(不良示例)。但是,声明是最佳答案,因为它有0个磁盘io。
Orwellophile 2013年

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

1
对我来说很棒。特别是因为我的shell没有类型的-t标志(我在使用类型“ $ command”时遇到很多麻烦)
Dennis

2
实际上,它还可以在zsh(对rc脚本有用)中工作,并且不需要grep作为类型。
Lloeki 2013年

2
@DennisHodapp不需要type -t,您可以依赖退出状态。我很久type program_name > /dev/null 2>&1 && program_name arguments || echo "error"以前就看我是否能够打电话。显然,type -tand上面的方法还允许检测类型,而不仅仅是其是否“可调用”。
0xC0000022L

@ 0xC0000022L如果program_name不是函数怎么办?
David Winiecki 2015年

2
@ 0xC0000022L我很想知道如何使用退出状态不能让您知道program_name是否是一个函数,但是现在我想您确实回答了,当您说“显然-t类型,并且上述方法也可以检测到该类型时,而不仅仅是它是否可调用。” 抱歉。
David Winiecki 2015年

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'选项是多余的。
Rajish 2013年

3
-Fzsh中不存在该选项(对可移植性有用)
Lloeki

-F其实也没有必要:它似乎只抑制函数定义/主体。
2016年

1
@blueyed可能不是必需的,但我们非常希望这样做,我们试图确认一个函数是否存在,而不是列出它的全部内容(有些效率低下)。您会使用检查文件是否存在cat "$fn" | wc -c吗?至于zsh,如果bash标记不给您提示,那么问题本身就应该存在。“确定bash中是否存在函数”。我还要进一步指出,虽然该-F选项在zsh中不存在,但也不会引起错误,因此,同时使用-f和-F允许在zshbash中都成功进行检查,否则该检查就不会成功。
Orwellophile

@Orwellophile -F在zsh中用于浮点数。我不明白为什么使用-Fbash会更好?我得到的印象是declare -f在bash中使用相同的效果(关于返回码)。
2016年

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

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


很好,我最喜欢这个小组!您是否也不想在参数周围加上双引号?就像[ $(type -t "$1")"" == 'function' ]
quickshift

谢谢@quickshiftin; 我不知道我是否想要这些双引号,但是您可能是对的,尽管..甚至可以使用需要将其引起来的名称来声明一个函数吗?
格雷戈里·约瑟夫

4
您正在使用bash,使用[[...]]代替[...]并摆脱引号hack。还有反引号叉,这很慢。使用declare -f $1 > /dev/null代替。
Lloeki 2013年

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

声明是helluvalot更快!


1
无需grep即可完成: test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
qneill 2015年

@qneill我在回答中做了更广泛的测试,
jarno

PIPE是最慢的元素。此测试不能与type和进行比较declaretype | grep与相比declare。这是一个很大的区别。
kyb

7

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

输出样式:

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

用法:

isFunction some_name && echo yes || echo no

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

退出状态样式:

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

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


3
为了获得最大的简洁使用isFunction() { declare -F "$1"; } >&-
尼尔

3
isFunction() { declare -F -- "$@" >/dev/null; }是我的建议。它也适用于名称列表(仅当全部为函数时才成功),以开头的名称不会出现任何问题,-而在我这一边(bash4.2.25),declare当用输出关闭输出时始终会失败>&-,因为它无法写入名称在这种情况下的标准输出
Tino

并且请注意,echo在某些平台上,有时会因“系统调用中断”而失败。在这种情况下,no如果check为true,则仍然可以输出“ check && echo yes || echo no” 。
蒂诺

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(未设置)

实际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如果zsh上不存在f,则不会返回非零值,但是bash是。小心使用它。declare -f f,另一方面,按预期方式将功能的定义附加到stdout上(可能会令人讨厌...)
Manoel Vilela

1
您是否尝试过test_type3 () { [[ $(type -t f) = function ]] ; }定义局部变量的边际成本(尽管<10%)
Oliver

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

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

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

2

我特别喜欢GrégoryJoseph的解决方案

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

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

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

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 = $(类型a); c = $ {b //是一个函数/}; [$?= 0] &&返回1 || 返回0; } – qneill
Alexx Roche
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.