从shell脚本导入函数

时间:2012-10-10 09:09:06

标签: 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函数但是只要我重定向source命令的输出,脚本中的函数就不会被解析,并且在陷阱处理程序中不可用

这有效,但我得到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

5 个答案:

答案 0 :(得分:54)

根据bash manpage的“Shell Builtin命令”部分,.又名source采用传递给源脚本的可选参数列表。你可以使用它来引入一个do-nothing选项。例如,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可能无法处理它 - 因此#!/bin/bash开头为unit.sh

答案 1 :(得分:19)

从Python中选择了这种技术,但这个概念在bash或任何其他shell中运行得很好......

我们的想法是将脚本的主代码部分转换为函数。然后在脚本的最后,我们放置一个'if'语句,只有在我们执行脚本时才调用该函数,但如果我们采购它则不会。然后我们从'runtests'脚本中显式调用script()函数,该脚本源自'script'脚本,因此包含其所有函数。

这依赖于以下事实:如果我们获取脚本,那么bash维护的环境变量$0(正在执行的脚本的名称)将是调用的名称(父)脚本(本例中为runtests),而不是源脚本。

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

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

  • $@计算传递给函数或的所有参数 脚本作为单个字符串。相反,我们使用$*,所有 参数将连接成一个字符串。
  • RUNNING="$(basename $0)"是必需的,因为$0始终包含at 至少是./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

<强> runtests

#!/bin/bash

source script 

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

答案 2 :(得分:5)

如果您正在使用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

答案 3 :(得分:0)

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

#!/bin/bash

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

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

答案 4 :(得分: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

~