【问题标题】:How to break out of a sourced Bash script's function如何突破源 Bash 脚本功能
【发布时间】:2016-02-26 22:47:17
【问题描述】:

我有一个来源的 Bash 脚本。获取此脚本时,它会在 Bash 脚本中运行一个函数。如果满足某个条件,此函数应终止脚本。在不终止脚本来源的 shell 的情况下如何做到这一点?

明确一点:我希望终止动作由源 shell 脚本中的函数完成,而不是在源 shell 脚本的主体中。我可以看到的问题是return 只是从函数返回到脚本的主体,而exit 1 终止了调用 shell。

以下最小示例说明了该问题:

main(){
    echo "starting test of environment..."
    ensure_environment
    echo "environment safe -- starting other procedures..."
}

ensure_environment(){
    if [ 1 == 1 ]; then
        echo "environment problemm -- terminating..."
        # exit 1 # <-- terminates calling shell
        return   # <-- returns only from function, not from sourced script
    fi
}

main

【问题讨论】:

  • 改为执行脚本。这样,exit 将退出它正在运行的子 shell。
  • 有趣。您能否举一个小例子开始,可以改进。例如。一个终止 shell。
  • 你要求的东西是不可能的。你必须重组你的代码。
  • @fedorqui 该脚本是为了设置大量环境变量而设计的,所以我认为必须 source 。
  • @LudwigSchulze 应要求,我添加了一个完整的最小脚本示例,该脚本在获取时可以说明问题。 return 不会终止源脚本(仅是函数),exit 1 会终止调用 shell。我需要一些介于两者之间的东西!

标签: bash return exit terminate


【解决方案1】:

您可以通过源 shell 脚本returnPOSIX spec

所以,虽然你不能直接从函数中return 得到你想要的东西,但如果你的函数返回非零(或其他一些值)可以从脚本主体返回商定的价值)。

例如:

$ cat foo.sh
f() {
    echo in f "$@"
}

e() {
    return 2
}

f 1
e
f 2
if ! e; then
    return
fi
f 3
$ . foo.sh
in f 1
in f 2

【讨论】:

  • 感谢您的解决方案。我知道这种方法。我的目的是找到一种从其函数中终止源脚本的方法,因为我无法在最小示例中真正包含的额外复杂性使得将终止过程集中在一个函数中是非常可取的。
  • @d3pd 你不能。不是没有子shell。这是不可能的。不过,您可以在函数中保留您想要的任何和所有逻辑。您只需要在ifensure_environment || return 或类似的东西中调用该函数。 ....您可能可以使用RETURN 陷阱检查$? 或其他任何实际变量来做一些事情。
【解决方案2】:

这是一个秘诀,您可以通过自己的方法实现目标。我不会为你写你的代码,只是描述它是如何完成的。

您的目标是通过有效地获取可能复杂的 shell 脚本来设置/更改当前 bash shell 中的环境变量。此脚本的某些组件可能会决定停止执行此源脚本。使这件事变得复杂的是,这个决定不一定是顶级的,但可能位于嵌套函数调用中。然后,return 没有帮助,exit 将终止采购 shell,这是不希望的。

你的这句话让你的任务变得更容易了:

我无法真正包含在最小示例中的其他复杂性 非常希望将终止程序集中在一个 函数。

这就是你的做法:

您可以使用另一个脚本“ipcscript.bash”,而不是获取决定将哪个环境设置为什么的真实脚本(“realscript.bash”)。

ipcscript.bash 将设置一些进程间通信。这可能是您使用 exec 打开的一些额外文件描述符上的管道,它可能是一个临时文件,也可能是其他文件。

ipcscript.bash 然后将realscript.bash 作为子进程启动。这意味着,realscript.bash 所做的环境更改首先只会影响 bash 的该子进程实例的环境。将realscript.bash 作为子进程启动,您可以获得在任何嵌套级别终止执行并退出而不终止源外壳的能力。

如您所写,您的退出调用将存在于一个集中式函数中,当决定终止执行时,该函数会从任何级别调用。您的终止函数现在需要在退出之前以合适的格式将当前环境写入 IPC 机制。

ipcscript.bash 将从 IPC 机制中读取环境设置,并在源 shell 的过程中重现所有设置。

【讨论】:

    【解决方案3】:

    这个怎么样:通过一个简单的包装器调用所有东西,这里是“ocall”,它维护一个全局状态,这里是“STILL_OK”

    STILL_OK=true
    
    ocall() {
        if $STILL_OK 
        then
           echo -- "$@" # this is for debugging, you can delete this line
           if "$@"
           then
              true 
           else
              STILL_OK=false
           fi
        fi
    }
    
    main(){
        ocall echo "starting test of environment..."
        ocall ensure_environment
        ocall echo "environment safe -- starting other procedures..."
    }
    
    ensure_environment(){
        if [ 1 == 1 ]; then
            ocall echo "environment problemm -- terminating..."
            # exit 1 # <-- terminates calling shell
            return 1  # <-- returns from sourced script but leaves sourcing shell running
        fi
    }
    
    ocall main
    

    【讨论】:

    • 为了让$@ 保留它的属性,它基本上应该总是被双引号引起来。在这里并不重要,但对我来说,它会立即引发新手警报,所以你可能只是出于这个原因想要修复它。
    • 对。当我可以使用 ruby​​ 时,我会避免使用 bash。已更正。
    • 吹毛求疵:if $STILL_OK 是一个安全漏洞。最好使用if [ "$STILL_OK" = "true" ],即使它不那么优雅。否则,如果您在没有设置STILL_OK 的情况下实现了任何通往该源代码行的路径,您将执行当前在您的环境中为STILL_OK 设置的任何内容。可能只有一个人永远不知道。
    【解决方案4】:

    这是不可能的。

    如果您源代码一个脚本(对于这里所涉及的方面),就像在调用(源代码)shell 中逐行输入一样。您想留下一个不存在的范围(源脚本),因此不能留下。

    我能想到的唯一方法是将退出愿望传递回调用函数并检查它:

    main() {
        echo "starting test of environment..."
        [ "$(ensure_environment)" = "bailout" ] && return
        echo "environment safe -- starting other procedures..."
    }
    
    ensure_environment() {
        if [ 1 == 1 ]; then
            echo "bailout"
            return
        fi
    }
    
    main
    

    您所要求的通常在其他语言中也是不可能的。通常每个函数只能终止自身(通过返回),而不是自身之外的更广泛定义的范围(如它所在的脚本)。 An exception to this rule is exception handling 使用 try/catch 或类似方法。

    还要考虑这一点:如果您对该脚本进行源代码处理,shell 函数将在源代码 shell 中变得已知。所以你可以稍后再给他们打电话。然后(再次)没有函数可以终止的周围范围。

    【讨论】:

      【解决方案5】:

      这是我更喜欢的解决方案(它有副作用,解释如下):

      #!/usr/bin/env bash
      # force inheritance of ERR trap inside functions and subshells
      shopt -s extdebug
      # pick custom error code to force script end
      CUSTOM_ERROR_CODE=13
      
      # clear ERR trap and set a new one
      trap - ERR
      trap '[[ $? == "$CUSTOM_ERROR_CODE" ]] && echo "IN TRAP" && return $CUSTOM_ERROR_CODE 2>/dev/null;' ERR
      
      # example function that triggers the trap, but does not end the script
      function RETURN_ONE() { return 1; }
      RETURN_ONE
      echo "RETURNED ONE"
      
      # example function that triggers the trap and ends the script
      function RETURN_CUSTOM_ERROR_CODE() { return "$CUSTOM_ERROR_CODE"; }
      # example function that indirectly calls the above function and returns success (0) after
      function INDIRECT_RETURN_CUSTOM_ERROR_CODE() { RETURN_CUSTOM_ERROR_CODE; return 0; }
      INDIRECT_RETURN_CUSTOM_ERROR_CODE
      echo "RETURNED CUSTOM ERROR CODE"
      
      # clear traps
      trap - ERR
      # disable inheritance of ERR trap inside functions and subshells
      shopt -u extdebug
      

      输出:

      # source source_global_trap.sh
      RETURNED ONE
      IN TRAP
      IN TRAP
      

      说明: 简而言之,代码为ERR 设置了一个trap,但是,在trap(作为第一条指令)内部检查CUSTOM_ERROR_CODE 的返回码,并从源脚本返回仅用于@ 的值987654328@(本例中任意选择为13)。这意味着在任何地方返回CUSTOM_ERROR_CODE(由于shopt -s extdebug,否则只是第一级函数/命令)应该产生结束脚本的预期结果。

      副作用:

      [01] CUSTOM_ERROR_CODE 中的错误代码可能被脚本控制之外的命令使用,因此可以强制脚本在没有明确指令的情况下结束。这应该很容易避免,但可能会引起一些不适。

      [02] 调用 shopt -s extdebug 可能会导致不需要的行为,具体取决于脚本中的其他因素。详情在这里:https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html

      [03] 更重要的是,这是在干净的环境中采购脚本的输出,三遍,一个接一个:

      # exec bash
      
      # source source_global_trap.sh
      RETURNED ONE
      IN TRAP
      IN TRAP
      
      # source source_global_trap.sh
      RETURNED ONE
      IN TRAP
      IN TRAP
      IN TRAP
      
      # source source_global_trap.sh
      RETURNED ONE
      IN TRAP
      IN TRAP
      IN TRAP
      

      关于为什么会发生这种情况(额外的trap 电话),我有几种理论,但没有确凿的解释。在我的测试过程中它没有造成问题,但强烈建议进行任何澄清。

      【讨论】:

        【解决方案6】:

        有时我会编写一个脚本,其中包含我想在脚本之外使用的便捷功能。在这种情况下,如果脚本运行,那么它会做它的事情。但是如果脚本是有源的,它只是将一些函数加载到源 shell 中。 我使用这种形式:

        #!/bin/bash
        
        # This function will be sourcable
        foo() {
          echo hello world
        }
        
        # end if being sourced
        if [[ $0 == bash ]]; then
          return
        fi
        
        # the rest of the script goes here
        

        【讨论】:

          【解决方案7】:

          可能的。

          像在任何编程语言中一样执行它并“引发异常”,这将传播到调用链:

          # cat r
          
          set -u
          
          err=
          
          inner () {
             # we want to bailaout at this point:
             # so we cause -u to kick in:
             err="reason: some problem in 'inner' function"
             i=$error_occurred
             echo "will not be called"
          }
          
          inner1 () {
             echo before_inner
             inner
             echo "will not be called"
          }
          
          
          main () {
             echo before_inner1
             inner1
             echo "will not be called"
          }
          
          echo before_func
          main || echo "even this is not shown"
          
          # this *will* be called now, like typing next statement on the terminal:
          echo after_main
          echo "${err:-}" # if we failed
          

          测试:

          # echo $$
          9655
          # . r  || true
          before_func
          before_inner1
          before_inner
          bash: error_occurred: unbound variable
          after_main
          reason: some problem in 'inner' function
          # echo $$
          9655
          

          您可以通过2&gt;/dev/null 消除错误,清除

          【讨论】:

            猜你喜欢
            • 2015-11-14
            • 1970-01-01
            • 2011-04-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-10-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多