218

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

我以前通过 greping 和 seding 源来做到这一点,但这似乎是错误的。有没有更优雅的方法来做到这一点?

编辑:以下片段就像一个魅力:

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

14 回答 14

229

像这样:[[ $(type -t foo) == function ]] && echo "Foo exists"

内置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
于 2008-09-17T18:00:15.283 回答
100

内置 bash 命令declare具有-F显示所有已定义函数名称的选项。如果给定名称参数,它将显示存在哪些函数,如果都存在,它将相应地设置状态:

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

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

$ f() { return; }
$ fn_exist f && echo yes || echo no
yes
于 2008-09-17T18:04:14.213 回答
45

如果 declare 比 test 快 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
于 2012-03-02T08:08:47.280 回答
20

借鉴其他解决方案和评论,我想出了这个:

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。

于 2012-01-25T11:34:09.403 回答
11

挖掘一个旧帖子......但我最近使用了这个并测试了描述的两种替代方案:

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

声明速度更快!

于 2010-02-12T15:57:48.127 回答
7

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

输出风格:

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

用法:

isFunction some_name && echo yes || echo no

但是,如果没有记忆,重定向到 null 比输出替换更快(说到,糟糕且过时的 `cmd` 方法应该被摒弃,而使用 $(cmd)。)并且由于如果找到,则声明返回 true/false/未找到,并且函数返回函数中最后一个命令的退出代码,因此通常不需要显式返回,并且由于检查错误代码比检查字符串值(甚至是空字符串)更快:

退出状态样式:

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

这可能是你能得到的最简洁和温和的。

于 2012-05-28T18:09:26.923 回答
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 系统 0m0,004s 退出代码 0

test_declare2 (f 是函数)

真实 0m0,042s 用户 0m0,022s 系统 0m0,017s 退出代码 0

test_type (f 是函数)

真正的 0m2,200s 用户 0m1,619s 系统 0m1,008s 退出代码 0

test_type2 (f 是函数)

真正的 0m0,746s 用户 0m0,534s 系统 0m0,237s 退出代码 0

test_declare (f 未设置)

真正的 0m0,040s 用户 0m0,029s 系统 0m0,010s 退出代码 1

test_declare2 (f 未设置)

真正的 0m0,038s 用户 0m0,038s 系统 0m0,000s 退出代码 1

test_type (f 未设置)

真正的 0m2,438s 用户 0m1,678s 系统 0m1,045s 退出代码 1

test_type2 (f 未设置)

真正的 0m0,805s 用户 0m0,541s 系统 0m0,274s 退出代码 1

test_declare (f 是字符串)

真正的 0m0,043s 用户 0m0,034s 系统 0m0,007s 退出代码 1

test_declare2 (f 是字符串)

真正的 0m0,039s 用户 0m0,035s 系统 0m0,003s 退出代码 1

test_type (f 是字符串)

真正的 0m2,394s 用户 0m1,679s 系统 0m1,035s 退出代码 1

test_type2 (f 是字符串)

真正的 0m0,851s 用户 0m0,554s 系统 0m0,294s 退出代码 1

所以declare -F f似乎是最好的解决方案。

于 2016-11-19T13:03:01.177 回答
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
于 2017-10-11T14:34:54.550 回答
3

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

fn_exists()
{
  type $1 >/dev/null 2>&1;
}
于 2009-10-08T22:46:38.930 回答
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
$
于 2016-01-31T17:50:44.513 回答
3

如果定义了函数的调用。

已知函数名。假设名称是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;

严格模式和前置条件检查
我们有set -e严格模式。
我们|| return在函数中使用前提条件。
这会强制终止我们的 shell 进程。

# Set a strict mode for script execution. The essence here is "-e"
set -euf +x -o pipefail

function run_if_exists(){
    my_function=$1

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

    $my_function
}

run_if_exists  non_existing_function
echo "you will never reach this code"

以上等价于

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

这会杀死你的进程。
在先决条件中使用|| { true; return; }代替|| return;来解决此问题。

    [[ "$(type -t my_function)" == 'function' ]] || { true; return; }
于 2021-02-09T00:01:35.397 回答
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
于 2009-10-08T22:18:08.660 回答
2

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

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

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

    if [ "$TYPE_RESULT" == 'function' ]; then
        return 0
    else
        return 1
    fi
}
于 2012-02-27T14:29:31.820 回答
0

可以在没有任何外部命令的情况下使用 'type',但你必须调用它两次,所以它的速度仍然是 'declare' 版本的两倍:

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

另外,这在 POSIX sh 中不起作用,因此除了琐事之外,它完全没有价值!

于 2010-06-25T21:09:36.483 回答