目前我正在做一些从bash执行的单元测试。单元测试在bash脚本中初始化、执行和清理。该脚本通常包含init()、execute()和cleanup()函数。但它们不是强制性的。我想测试它们是否有定义。
我以前通过greping和seding源代码做到了这一点,但这似乎是错误的。还有更优雅的方式吗?
编辑:下面这句话很有魔力:
fn_exists()
{
LC_ALL=C type $1 | grep -q 'shell function'
}
目前我正在做一些从bash执行的单元测试。单元测试在bash脚本中初始化、执行和清理。该脚本通常包含init()、execute()和cleanup()函数。但它们不是强制性的。我想测试它们是否有定义。
我以前通过greping和seding源代码做到了这一点,但这似乎是错误的。还有更优雅的方式吗?
编辑:下面这句话很有魔力:
fn_exists()
{
LC_ALL=C type $1 | grep -q 'shell function'
}
像这样:[[$(type -t foo) == function]] && echo " foo存在"
内置类型命令将告诉您某个东西是函数、内置函数、外部命令还是没有定义。
附加的例子:
$ 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
内置bash命令声明有一个选项-F,显示所有定义的函数名。如果给出了name参数,它将显示这些函数中哪些存在,如果都存在,它将相应地设置状态:
$ 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
我会将其改进为:
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
挖掘一根旧柱子……但我最近使用了这个方法,并测试了以下两种替代方法:
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
声明要快得多!
可以在没有任何外部命令的情况下使用'type',但你必须调用它两次,所以它的速度仍然是'declare'版本的两倍:
test_function () {
! type -f $1 >/dev/null 2>&1 && type -t $1 >/dev/null 2>&1
}
加上这不能在POSIX sh中工作,所以它完全没有价值,除了作为琐事!
借鉴其他解决方案和评论,我得出了以下结论:
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。
我特别喜欢Grégory Joseph的解决方案
但我对它做了一点修改,以克服“双引号丑陋的把戏”:
function is_executable()
{
typeset TYPE_RESULT="`type -t $1`"
if [ "$TYPE_RESULT" == 'function' ]; then
return 0
else
return 1
fi
}
如果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
它可以归结为使用“declare”来检查输出或退出代码。
输出方式:
isFunction() { [[ "$(declare -Ff "$1")" ]]; }
用法:
isFunction some_name && echo yes || echo no
然而,如果内存有用,重定向到null比输出替换更快(说到这里,可怕的过时的' cmd '方法应该被摒弃,取而代之的是$(cmd)。)由于declare在找到/未找到时返回true/false,并且函数返回函数中最后一个命令的退出码,因此通常不需要显式返回,并且由于检查错误代码比检查字符串值(甚至是空字符串)更快:
退出状态样式:
isFunction() { declare -Ff "$1" >/dev/null; }
这可能是你能得到的最简洁和最温和的说法了。
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
$
测试不同的解决方案:
#!/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似乎是最好的解决方案。
来自我对另一个答案的评论(当我回到这个页面时,我总是忽略这个答案)
$ 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;
严格的模式和前提条件检查 我们将-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;返回;}而不是||返回;在前提条件下解决这个问题。
[[ "$(type -t my_function)" == 'function' ]] || { true; return; }
你可以用四种方法检查它们
fn_exists() { type -t $1 >/dev/null && echo 'exists'; }
fn_exists() { declare -F $1 >/dev/null && echo 'exists'; }
fn_exists() { typeset -F $1 >/dev/null && echo 'exists'; }
fn_exists() { compgen -A function $1 >/dev/null && echo 'exists'; }