如何打破源Bash脚本的功能

时间:2015-11-24 13:09:45

标签: bash return exit terminate

我有一个源代码的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

7 个答案:

答案 0 :(得分:8)

您可以从源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

答案 1 :(得分:2)

这个怎么样:通过一个简单的包装器调用一切,这里&#34; ocall&#34;,它维持一个全局状态,这里&#34; STILL_OK&#34;

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

答案 2 :(得分:2)

这是不可能的。

如果您提供脚本,那么(对于此处涉及的方面)就像在调用(sourcing)shell中逐个输入每一行一样。您想要保留一个不存在的范围(源脚本),因此不能将其保留。

我能想到的唯一方法是将exit-wish传递回调用函数并检查它:

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中已知。所以你可以稍后再打电话给他们。然后(再次)没有函数可以终止的周围范围。

答案 3 :(得分:2)

这是一个如何使用您的方法实现目标的方法。我不会为你编写代码,只是描述它是如何完成的。

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

您的任务通过以下声明变得更容易:

  

我可以在一个最小的例子中真正包含的额外复杂性   非常希望将终止程序集中在一个   功能

您就是这样做的:

您可以使用另一个脚本&#34; realscript.bash&#34;而不是寻找决定将哪个环境设置为什么(&#34; ipcscript.bash&#34;)的真实脚本。

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

然后

ipcscript.bashrealscript.bash作为子进程启动。这意味着,realscript.bash首先执行的环境更改只会影响bash子进程实例的环境。将realscript.bash作为子进程启动,您可以使用exit终止任何嵌套级别的执行,而不会终止源代码shell。

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

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

答案 4 :(得分:2)

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

#!/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

说明: 简而言之,该代码为 trap 设置了一个 ERR,但是,在 trap 内部(作为第一条指令)根据 CUSTOM_ERROR_CODE 检查返回代码并从源脚本返回仅适用于并具有 CUSTOM_ERROR_CODE 的值(在本例中任意选择为 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 调用),我有几种理论,但没有结论性的解释。它在我的测试期间没有造成麻烦,但强烈建议您进行任何澄清。

答案 5 :(得分:1)

有时我会编写一个脚本,它具有我想在脚本之外使用的便捷功能。在这种情况下,如果脚本运行,那么它就可以了。但是如果脚本是源代码的,它只是将一些函数加载到源代码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

答案 6 :(得分:0)

可能。

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

# 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>/dev/null消除错误,清除