目前我正在进行一些从bash执行的单元测试。单元测试在bash脚本中初始化、执行和清理。这个脚本通常包含一个init()、execute()和cleanup()函数,但它们不是必需的。我想测试它们是否被定义。
我之前通过grep和sed源代码来实现这个功能,但感觉不太对。有没有更优雅的方法?
编辑:下面的代码片段非常好用:
fn_exists()
{
LC_ALL=C type $1 | grep -q 'shell function'
}
像这样:[[ $(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
type -t $function
是解决问题的关键。 - Allan Windtype [-t]
命令可以很好地告诉你一个东西是什么,但是如果要测试某个东西是否是函数,则较慢,因为你必须使用管道到 grep 或使用反引号,这两种方法都会生成一个子进程。 - Lloekitype -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile
(不好的例子)。然而,declare是最好的答案,因为它没有磁盘io。 - Orwellophile内置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
type -t
,你可以依赖于退出状态。我长期以来一直使用 type program_name > /dev/null 2>&1 && program_name arguments || echo "error"
来查看是否能够调用某些东西。显然,type -t
和上述方法也允许检测类型,而不仅仅是它是否“可调用”。 - 0xC0000022L如果声明比测试快10倍,那么这似乎是显而易见的答案。
编辑:下面,对于BASH来说,-f
选项是多余的,可以不用。个人而言,我很难记住哪个选项是做什么的,所以我两个选项都使用。-f显示函数,-F显示函数名。
#!/bin/sh
function_exists() {
declare -f -F $1 > /dev/null
return $?
}
function_exists function_name && echo Exists || echo No such function
在使用declare时,"-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
-F
选项(这个选项对于移植很有用)。 - Lloekicat "$fn" | wc -c
命令来检查文件是否存在吗?至于zsh,如果“bash”标签没有让您明白,也许问题本身应该有所提示。“确定 bash 中是否存在一个函数”。我还要指出,尽管 zsh 中不存在 -F
选项,但它也不会导致错误,因此同时使用 -f
和 -F
可以使检查在 zsh 和 bash 中都成功,否则将无法实现。 - Orwellophile-F
用于浮点数。我不明白在bash中使用-F
有什么好处?!我认为declare -f
在bash中的作用与返回代码相同。 - blueyed-F
会有害,但显然并不是这样。 - blueyed借鉴其他方案和评论,我得出了以下解决方案:
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[[...]]
替代[...]
并且去掉引号技巧。此外,反引号会导致进程分叉,从而降低速度。改用declare -f $1 > /dev/null
。 - Lloekifn_exists() { [ x$(type -t $1) = xfunction ]; }
- qneill翻译如下:挖掘一篇旧帖子……但我最近使用过它,并测试了描述的两种替代方案:
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
使用 declare 速度更快!
test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }
- qneill测试不同的解决方案:
#!/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.055秒 用户 0m0.041秒 系统 0m0.004秒 退出代码 0
test_declare2 (f 是函数)
实际 0m0.042秒 用户 0m0.022秒 系统 0m0.017秒 退出代码 0
test_type (f 是函数)
实际 0m2.200秒 用户 0m1.619秒 系统 0m1.008秒 退出代码 0
test_type2 (f 是函数)
实际 0m0.746秒 用户 0m0.534秒 系统 0m0.237秒 退出代码 0
test_declare (f 未设置)
实际 0m0.040秒 用户 0m0.029秒 系统 0m0.010秒 退出代码 1
test_declare2 (f 未设置)
实际 0m0.038秒 用户 0m0.038秒 系统 0m0.000秒 退出代码 1
test_type (f 未设置)
实际 0m2.438秒 用户 0m1.678秒 系统 0m1.045秒 退出代码 1
test_type2 (f 未设置)
实际 0m0.805秒 用户 0m0.541秒 系统 0m0.274秒 退出代码 1
test_declare (f 是字符串)
实际 0m0.043秒 用户 0m0.034秒 系统 0m0.007秒 退出代码 1
test_declare2 (f 是字符串)
实际 0m0.039秒 用户 0m0.035秒 系统 0m0.003秒 退出代码 1
test_type (f 是字符串)
实际 0m2.394秒 用户 0m1.679秒 系统 0m1.035秒 退出代码 1
test_type2 (f 是字符串)
实际 0m0.851秒 用户 0m0.554秒 系统 0m0.294秒 退出代码 1
declare -F f
似乎是最佳解决方案。declare -F f
不会返回非零值,但在bash中会。使用时要小心。另一方面,declare -f f
按预期工作,将函数定义附加到stdout上(这可能很烦人...)。 - Manoel Vilelatest_type3 () { [[ $(type -t f) = function ]] ; }
吗?定义本地变量的成本很小(虽然不到10%)。 - Oliver这归结于使用“declare”来检查输出或退出代码。
输出样式:
isFunction() { [[ "$(declare -Ff "$1")" ]]; }
使用方法:
isFunction some_name && echo yes || echo no
isFunction() { declare -Ff "$1" >/dev/null; }
isFunction() { declare -F "$1"; } >&-
。该函数用于判断某个输入是否是一个函数,并返回相应的结果。注意,翻译不能改变原文的意思,但需要让内容更加通俗易懂。 - NeilisFunction() { declare -F -- "$@" >/dev/null; }
是我的建议。它可用于名称列表(仅当所有名称都是函数时才成功),对于以“-”开头的名称没有问题,并且在我这里(使用 bash
4.2.25),当输出关闭时,declare
始终会失败,因为在这种情况下无法将名称写入标准输出。 - Tino以下是我在另一个答案中的评论(每次回到这个页面时都会错过)
$ 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
已知函数名称。假设函数名为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; }
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
$
fn_exists foo || foo() { :; }
- Harveytype -t
和==
,您可以简化grep的命令。 - Roland WeberLC_ALL=C
来解决问题。 - gaRex