如何突破 sourced Bash 脚本的功能
How to break out of a sourced Bash script's function
我有一个 Bash 源脚本。获取此脚本后,它会在 Bash 脚本中运行一个函数。如果满足特定条件,此函数应终止脚本。如何在不终止脚本来源的 shell 的情况下完成此操作?
明确一点:我希望终止操作由来源 shell 脚本中的函数完成,而不是来源 shell 脚本的主体。我可以看到的问题是 return
只是 returns 从函数到脚本的主体,而 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
您可以 return
从来源 shell 脚本。 POSIX spec
因此,虽然您不能 return
直接从函数中获取您想要的内容,但您 可以 return 从脚本的主体中获取如果您的函数 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
这个怎么样:通过一个简单的包装器调用所有内容,此处 "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
不可能。
如果您获取一个脚本(对于此处涉及的方面),就像在调用(获取)中逐行输入每一行 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 中已知。所以你可以稍后再打电话给他们。然后(再次)没有函数可以终止的周围范围。
这是一个如何通过您的方法实现目标的秘诀。我不会为您编写代码,只是描述它是如何完成的。
您的目标是 set/alter 当前 bash shell 中的环境变量,有效地获取可能复杂的 shell 脚本。该脚本的某些组件可能会决定停止执行该源脚本。使这变得复杂的是这个决定不一定是顶级的,但可能位于嵌套函数调用中。 return
,然后,没有帮助,exit
将终止采购 shell,这是不希望的。
你的这句话使你的任务变得更容易:
additional complexity that I can't really include in a minimal example
makes it very desirable to centralise the termination procedure in a
function.
这是你的做法:
您不是获取决定将哪个环境设置为什么的真实脚本 ("realscript.bash
"),而是获取另一个脚本“ipcscript.bash
”。
ipcscript.bash
将设置一些进程间通信。这可能是你用 exec 打开的一些额外文件描述符的管道,它可能是一个临时文件,它可能是别的东西。
ipcscript.bash
然后将 realscript.bash
作为子进程启动。这意味着,realscript.bash
首先执行的环境更改只会影响 bash 的子进程实例的环境。将 realscript.bash
作为子进程启动,您可以获得在任何嵌套级别使用 exit 终止执行而不终止源 shell.
的能力
正如您所写,您对退出的调用将存在于一个集中函数中,当决定终止执行时,该函数会从任何级别调用。您的终止函数现在需要在退出之前将当前环境以合适的格式写入 IPC 机制。
ipcscript.bash
将从IPC机制中读取环境设置并重现采购过程中的所有设置shell。
有时我编写的脚本具有方便的功能,我想在脚本之外使用这些功能。在这种情况下,如果脚本是 运行,那么它会执行它的操作。但是如果脚本是 source 的,它只是将一些函数加载到 sourcing shell。
我使用这种形式:
#!/bin/bash
# This function will be sourcable
foo() {
echo hello world
}
# end if being sourced
if [[ [=10=] == bash ]]; then
return
fi
# the rest of the script goes here
是可能的。
像在任何编程语言中那样做,"raise an exception" 它将向上传播调用链:
# 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
、清除
消除错误
这是我更喜欢的解决方案(它有副作用,解释如下):
#!/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
内部(作为第一条指令)检查 return 代码与 CUSTOM_ERROR_CODE
和 returns 来自源脚本,仅用于 CUSTOM_ERROR_CODE
的值(在本例中任意选择为 13
)。这意味着 returning CUSTOM_ERROR_CODE
任何地方(由于 shopt -s extdebug
,否则只有第一级 functions/commands)应该产生结束脚本的预期结果。
副作用:
[01] CUSTOM_ERROR_CODE
中的错误代码可能被脚本控制之外的命令使用,因此可以在没有明确指示的情况下强制脚本结束这样做。这应该很容易避免,但会引起一些不适。
[02] 调用 shopt -s extdebug
可能会导致不需要的行为,具体取决于脚本中的其他因素。详情在这里:https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
[03]更重要的是,这是在干净的环境下sourc脚本的输出,三次,一次又一次:
# 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
调用),我有几种理论,但没有确凿的解释。它在我的测试期间没有造成问题,但强烈建议进行任何澄清。
我有一个 Bash 源脚本。获取此脚本后,它会在 Bash 脚本中运行一个函数。如果满足特定条件,此函数应终止脚本。如何在不终止脚本来源的 shell 的情况下完成此操作?
明确一点:我希望终止操作由来源 shell 脚本中的函数完成,而不是来源 shell 脚本的主体。我可以看到的问题是 return
只是 returns 从函数到脚本的主体,而 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
您可以 return
从来源 shell 脚本。 POSIX spec
因此,虽然您不能 return
直接从函数中获取您想要的内容,但您 可以 return 从脚本的主体中获取如果您的函数 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
这个怎么样:通过一个简单的包装器调用所有内容,此处 "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
不可能。
如果您获取一个脚本(对于此处涉及的方面),就像在调用(获取)中逐行输入每一行 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 中已知。所以你可以稍后再打电话给他们。然后(再次)没有函数可以终止的周围范围。
这是一个如何通过您的方法实现目标的秘诀。我不会为您编写代码,只是描述它是如何完成的。
您的目标是 set/alter 当前 bash shell 中的环境变量,有效地获取可能复杂的 shell 脚本。该脚本的某些组件可能会决定停止执行该源脚本。使这变得复杂的是这个决定不一定是顶级的,但可能位于嵌套函数调用中。 return
,然后,没有帮助,exit
将终止采购 shell,这是不希望的。
你的这句话使你的任务变得更容易:
additional complexity that I can't really include in a minimal example makes it very desirable to centralise the termination procedure in a function.
这是你的做法:
您不是获取决定将哪个环境设置为什么的真实脚本 ("realscript.bash
"),而是获取另一个脚本“ipcscript.bash
”。
ipcscript.bash
将设置一些进程间通信。这可能是你用 exec 打开的一些额外文件描述符的管道,它可能是一个临时文件,它可能是别的东西。
ipcscript.bash
然后将 realscript.bash
作为子进程启动。这意味着,realscript.bash
首先执行的环境更改只会影响 bash 的子进程实例的环境。将 realscript.bash
作为子进程启动,您可以获得在任何嵌套级别使用 exit 终止执行而不终止源 shell.
正如您所写,您对退出的调用将存在于一个集中函数中,当决定终止执行时,该函数会从任何级别调用。您的终止函数现在需要在退出之前将当前环境以合适的格式写入 IPC 机制。
ipcscript.bash
将从IPC机制中读取环境设置并重现采购过程中的所有设置shell。
有时我编写的脚本具有方便的功能,我想在脚本之外使用这些功能。在这种情况下,如果脚本是 运行,那么它会执行它的操作。但是如果脚本是 source 的,它只是将一些函数加载到 sourcing shell。 我使用这种形式:
#!/bin/bash
# This function will be sourcable
foo() {
echo hello world
}
# end if being sourced
if [[ [=10=] == bash ]]; then
return
fi
# the rest of the script goes here
是可能的。
像在任何编程语言中那样做,"raise an exception" 它将向上传播调用链:
# 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
、清除
这是我更喜欢的解决方案(它有副作用,解释如下):
#!/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
内部(作为第一条指令)检查 return 代码与 CUSTOM_ERROR_CODE
和 returns 来自源脚本,仅用于 CUSTOM_ERROR_CODE
的值(在本例中任意选择为 13
)。这意味着 returning CUSTOM_ERROR_CODE
任何地方(由于 shopt -s extdebug
,否则只有第一级 functions/commands)应该产生结束脚本的预期结果。
副作用:
[01] CUSTOM_ERROR_CODE
中的错误代码可能被脚本控制之外的命令使用,因此可以在没有明确指示的情况下强制脚本结束这样做。这应该很容易避免,但会引起一些不适。
[02] 调用 shopt -s extdebug
可能会导致不需要的行为,具体取决于脚本中的其他因素。详情在这里:https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
[03]更重要的是,这是在干净的环境下sourc脚本的输出,三次,一次又一次:
# 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
调用),我有几种理论,但没有确凿的解释。它在我的测试期间没有造成问题,但强烈建议进行任何澄清。