【问题标题】:Importing functions from a shell script从 shell 脚本导入函数
【发布时间】:2012-10-10 09:09:05
【问题描述】:

我有一个 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

【问题讨论】:

    标签: shell


    【解决方案1】:

    根据bash manpage 的“Shell 内置命令”部分,. 又名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 可能无法处理它——因此#!/bin/bash 位于unit.sh 的开头。

    【讨论】:

    • 您可能想要shift 参数列表,因此main 不会与--source-only 联系
    • @HubertGrzeskowiak 好点,已修复。感谢您的建议!
    • shift 在那里没有意义,因为main 仅在--source-only 不是第一个参数时运行。
    • @HelderPereira 好收获!感谢您报告。
    【解决方案2】:

    从 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
    

    【讨论】:

    • 这有两个缺点:无论何时重命名脚本,都必须更改其内容;它不适用于符号链接(不确定别名)。
    • 如果从命令行执行会抛出错误:basename: illegal option -- b.
    【解决方案3】:

    如果您使用的是 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_SOURCE[@]} == 1 ));简短的条件 (( ${#BASH_SOURCE[@]} == 1 )) && main "$@" 也应该可以工作,如果你不希望它做任何事情 else 就足够了。
    【解决方案4】:

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

    #!/bin/bash
    
    foo () { ... }
    bar () { ... }
    
    [[ "${FUNCNAME[0]}" == "source" ]] && return
    code
    

    【讨论】:

      【解决方案5】:

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

      ~

      【讨论】:

        猜你喜欢
        • 2012-11-28
        • 2021-02-25
        • 1970-01-01
        • 2012-01-23
        • 2014-04-02
        • 1970-01-01
        • 2012-03-08
        相关资源
        最近更新 更多