从 shell 脚本导入函数

我有一个 shell 脚本,我想用 shUnit 测试它。这个脚本(和所有函数)都在一个文件中,因为它使安装更加容易。

例如 script.sh

#!/bin/sh


foo () { ... }
bar () { ... }


code

我想编写第二个文件(不需要分发和安装)来测试在 script.sh中定义的函数

就像 run_tests.sh

#!/bin/sh


. script.sh


# Unit tests

现在问题在于 .(或 Bash 中的 source)。它不仅解析函数定义,还执行脚本中的代码。

因为没有参数的脚本没有什么不好的地方

. script.sh > /dev/null 2>&1

但我在想是否有更好的方法来实现我的目标。

剪辑

我提出的解决方案在源脚本调用 exit的情况下不起作用,因此我必须捕获退出

#!/bin/sh


trap run_tests ERR EXIT


run_tests() {
...
}


. script.sh

调用了 run_tests函数,但是只要我重定向源命令的输出,脚本中的函数就不会被解析,陷阱处理程序中的函数也不可用

这样可以,但是我得到了 script.sh的输出:

#!/bin/sh
trap run_tests ERR EXIT
run_tests() {
function_defined_in_script_sh
}
. script.sh

这不打印输出,但我得到一个错误,该函数没有定义:

#!/bin/sh
trap run_tests ERR EXIT
run_tests() {
function_defined_in_script_sh
}
. script.sh | grep OUTPUT_THAT_DOES_NOT_EXISTS

这不会打印输出,也不会调用 run_tests陷阱处理程序:

#!/bin/sh
trap run_tests ERR EXIT
run_tests() {
function_defined_in_script_sh
}
. script.sh > /dev/null
77107 次浏览

根据 Bash 手册的“ Shell Builtin Commands”部分,. aka source接受一个可选的参数列表,这些参数被传递给正在获取源代码的脚本。你可以利用这一点来引入无为选项。例如,script.sh可以是:

#!/bin/sh


foo() {
echo foo $1
}


main() {
foo 1
foo 2
}


if [ "${1}" != "--source-only" ]; then
main "${@}"
fi

以及 unit.sh可以是:

#!/bin/bash


. ./script.sh --source-only


foo 3

然后 script.sh将正常工作,而 unit.sh将可以访问 script.sh中的所有函数,但是不会调用 main()代码。

请注意,source的额外参数不在 POSIX,因此 /bin/sh可能无法处理它ーー因此在 unit.sh的开头是 #!/bin/bash

从 Python 中学到了这项技术,但是这个概念在 bash 或任何其他 shell 中都可以很好地工作..。

我们的想法是将脚本的主代码段转换为一个函数。然后在脚本的最后,我们放置了一个“ if”语句,它只会在执行脚本时调用该函数,而在源代码时不会调用该函数。然后我们从‘ runtest’脚本中显式地调用 script ()函数,这个脚本来源于‘ script’脚本,因此包含了它的所有函数。

这取决于这样一个事实,即如果我们对脚本进行源代码管理,bash 维护的环境变量 $0(即正在执行的脚本的名称)将是 打来的(父)脚本的名称(在本例中为 runtests) ,而不是源代码。

(我已经将 script.sh重命名为 script,因为 .sh是多余的,让我感到困惑。 : -)

下面是两个脚本。一些注释..。

  • $@计算传递给函数或 如果使用的是 $*,则所有 参数将被连接到一个字符串中。
  • RUNNING="$(basename $0)"是必需的,因为 $0总是包括 至少是工作目录的前缀。
  • 测试 if [[ "$RUNNING" == "script" ]]...。是魔术,导致 仅当 script直接运行时才调用 script ()函数 来自指挥部。

剧本

#!/bin/bash


foo ()    { echo "foo()"; }


bar ()    { echo "bar()"; }


script () {
ARG1=$1
ARG2=$2
#
echo "Running '$RUNNING'..."
echo "script() - all args:  $@"
echo "script() -     ARG1:  $ARG1"
echo "script() -     ARG2:  $ARG2"
#
foo
bar
}


RUNNING="$(basename $0)"


if [[ "$RUNNING" == "script" ]]
then
script "$@"
fi

运行测试

#!/bin/bash


source script


# execute 'script' function in sourced file 'script'
script arg1 arg2 arg3

如果使用 Bash,可以使用 BASH_SOURCE数组实现与@andrewdotn 方法类似的解决方案(但不需要额外的标志或取决于脚本名称)。

Script.sh:

#!/bin/bash


foo () { ... }
bar () { ... }


main() {
code
}


if [[ "${#BASH_SOURCE[@]}" -eq 1 ]]; then
main "$@"
fi

Run _ tests.sh:

#!/bin/bash


. script.sh


# Unit tests

如果您正在使用 Bash,另一个解决方案可能是:

#!/bin/bash


foo () { ... }
bar () { ... }


[[ "${FUNCNAME[0]}" == "source" ]] && return
code

假设我们的 shell 库文件是下面这个名为 aLib.sh 的文件:

funcs=("a" "b" "c")                   # File's functions' names
for((i=0;i<${#funcs[@]};i++));        # Avoid function collision with existing
do
declare -f "${funcs[$i]}" >/dev/null
[ $? -eq 0 ] && echo "!!ATTENTION!! ${funcs[$i]} is already sourced"
done


function a(){
echo function a
}
function b(){
echo function b
}
function c(){
echo function c
}




if [ "$1" == "--source-specific" ];      # Source only specific given as arg
then
for((i=0;i<${#funcs[@]};i++));
do
for((j=2;j<=$#;j++));
do
anArg=$(eval 'echo ${'$j'}')
test "${funcs[$i]}" == "$anArg" && continue 2
done
unset ${funcs[$i]}
done
fi
unset i j funcs

在开始时,它检查并警告任何检测到的函数名冲突。 最后,bash 已经为所有函数提供了源代码,因此它从这些函数中释放了内存,并只保留选中的函数。

可以这样使用:

 user@pc:~$ source aLib.sh --source-specific a c
user@pc:~$ a; b; c
function a
bash: b: command not found
function c

~