78

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

示例script.sh

#!/bin/sh

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

code

我想写第二个文件(不需要分发和安装)来测试定义的功能script.sh

就像是run_tests.sh

#!/bin/sh

. script.sh

# Unit tests

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

由于没有参数的脚本没有什么不好的,我可以

. 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
4

5 回答 5

114

根据bash 手册页的“Shell Builtin Commands”部分,.akasource接受一个可选的参数列表,这些参数传递给正在获取的脚本。您可以使用它来引入无操作选项。例如,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()代码。

请注意,额外的参数 tosource不在 POSIX 中,因此/bin/sh可能无法处理它 - 因此#!/bin/bashunit.sh.

于 2012-11-12T19:22:51.690 回答
23

从 Python 中学习了这种技术,但这个概念在 bash 或任何其他 shell 中都可以正常工作......

这个想法是我们将脚本的主要代码部分变成一个函数。然后在脚本的最后,我们放置一个“if”语句,它只会在我们执行脚本时调用该函数,而不是在我们获取它时调用该函数。然后,我们从我们的“runtests”脚本中显式调用 script() 函数,该脚本已经获取了“script”脚本,因此包含了它的所有函数。

这依赖于这样一个事实,如果我们获取脚本,bash 维护的环境变量$0,即正在执行的脚本的名称,将是调用(父)脚本的名称(runtests在这种情况下),而不是源脚本.

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

下面是两个脚本。一些笔记...

  • $@计算所有作为单独字符串传递给函数或脚本的参数。如果相反,我们使用$*,所有参数将连接在一起成为一个字符串。
  • RUNNING="$(basename $0)"是必需的,因为$0始终至少包括当前目录前缀,如./script.
  • 测试if [[ "$RUNNING" == "script" ]]...。是仅在直接从命令行运行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
于 2012-11-13T12:07:26.237 回答
13

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

脚本.sh:

#!/bin/bash

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

main() {
    code
}

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

run_tests.sh:

#!/bin/bash

. script.sh

# Unit tests
于 2017-09-21T12:29:54.100 回答
1

如果您使用的是 Bash,另一种解决方案可能是:

#!/bin/bash

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

[[ "${FUNCNAME[0]}" == "source" ]] && return
code
于 2018-12-29T19:04:41.793 回答
0

我设计了这个。假设我们的 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

~

于 2021-01-27T20:18:29.820 回答