以下是我正在编写的脚本的简化方案。程序必须以不同的方式接收参数,因此需要将其细分为几个函数。
问题在于,当结果需要检查以显示消息时,来自更深层函数的返回值链路陷入陷阱而中断。
如果您尝试按原样运行脚本,您应该会看到:
这意味着糟糕的结果触发了陷阱(!),但设置它的母函数没有传递结果。在试图设置黑客时,我尝试了以下几种方法:在return_trap()中将retval设置为全局变量declare -g retval=$?,并在设置陷阱的行中使用其值。变量已设置([ -v retval ]返回成功),但是...没有值。有趣。好吧,让我们在check_params()中把retval=Eeh放到return_trap()之外,并将其作为通常参数设为$?。不,函数中的retval不会为全局变量设置值,它仍然是“Eeh”。不,没有local指令。默认情况下应将其视为全局变量。如果在check_params()中放置test=1,在check_a_param()中放置test=3,然后在setup()末尾用echo $test打印,您应该看到3。至少我是这样的。在这里,declare -g没有任何区别,这是预期的。也许这是函数的作用域?不是这样的。将return_trap()与declare -g retval=Eeh一起移动没有任何区别。当现代软件失败时,是时候回归老式写文件的方法了。让我们在return_trap()中使用retval=$?; echo $retval >/tmp/t将retval打印到/tmp/t中,然后使用trap "return_trap; trap - RETURN; return $(< /tmp/t)" RETURN从文件中读取它。现在我们终于可以看到,最后一个读取文件中数字的return指令实际上返回3。但是check_params()仍然返回0!
无论
问题在于,当结果需要检查以显示消息时,来自更深层函数的返回值链路陷入陷阱而中断。
#! /usr/bin/env bash
check_a_param() {
[ "$1" = return_ok ] && return 0 || return 3
}
check_params() {
# This trap should catch negative results from the functions
# performing actual checks, like check_a_param() below.
return_trap() {
local retval=$?
[ $retval -ne 0 ] && echo 'Bad, bad… Dropping to manual setup.'
return $retval
}
# check_params can be called from different functions, not only
# setup(). But the other functions don’t care about the return value
# of check_params().
[ "${FUNCNAME[1]}" = setup ] \
&& trap "return_trap; got_retval=$?; trap - RETURN; return $got_retval;" RETURN
check_a_param 'return_bad' || return $?
# …
# Here we check another parameters in the same way.
# …
echo 'Provided parameters are valid.'
return 0 # To be sure.
}
ask_for_params() {
echo 'User sets params manually step by step.'
}
setup() {
[ "$1" = force_manual ] && local MANUAL=t
# If gathered parameters do not pass check_params()
# the script shall resort to asking user for entering them.
[ ! -v MANUAL ] && {
check_params \
&& echo "check_params() returned with 0. Not running manual setup."
|| false
}|| ask_for_params
# do_the_job
}
setup "$@" # Either empty or ‘force_manual’.
它应该如何工作:
↗ 3 → 3→ trap →3 ↗ || ask_for_params ↘
check_a_param >>> check_params >>> [ ! -v MANUAL ] ↓
↘ 0 → 0→ trap →0 ↘ && ____________ do_the_job
这个想法是,如果检查失败,它的返回代码会强制让 check_params()
也返回,进而触发 setup()
中的条件 || ask_for_params
。但是陷阱返回值为0:
↗ 3 → 3→ trap →0
check_a_param >>> check_params >>> [ ! -v MANUAL ] &&… >>> do_the_job
↘ 0 → 0→ trap →0
如果您尝试按原样运行脚本,您应该会看到:
Bad, bad… Dropping to manual setup.
check_params() returned with 0. Not running manual setup.
这意味着糟糕的结果触发了陷阱(!),但设置它的母函数没有传递结果。在试图设置黑客时,我尝试了以下几种方法:在return_trap()中将retval设置为全局变量declare -g retval=$?,并在设置陷阱的行中使用其值。变量已设置([ -v retval ]返回成功),但是...没有值。有趣。好吧,让我们在check_params()中把retval=Eeh放到return_trap()之外,并将其作为通常参数设为$?。不,函数中的retval不会为全局变量设置值,它仍然是“Eeh”。不,没有local指令。默认情况下应将其视为全局变量。如果在check_params()中放置test=1,在check_a_param()中放置test=3,然后在setup()末尾用echo $test打印,您应该看到3。至少我是这样的。在这里,declare -g没有任何区别,这是预期的。也许这是函数的作用域?不是这样的。将return_trap()与declare -g retval=Eeh一起移动没有任何区别。当现代软件失败时,是时候回归老式写文件的方法了。让我们在return_trap()中使用retval=$?; echo $retval >/tmp/t将retval打印到/tmp/t中,然后使用trap "return_trap; trap - RETURN; return $(< /tmp/t)" RETURN从文件中读取它。现在我们终于可以看到,最后一个读取文件中数字的return指令实际上返回3。但是check_params()仍然返回0!
++ trap - RETURN
++ return 3
+ retval2=0
+ echo 'check_params() returned with 0. Not running manual setup.'
check_params() returned with 0. Not running manual setup.
如果trap
命令的参数严格为函数名,则返回原始结果。原始结果,而不是return_trap()
返回的结果。我尝试增加结果,但仍然得到3。
你可能会问:“为什么你需要这么频繁地取消陷阱?”这是为了避免另一个bug,该bug导致在从另一个函数调用check_params()
时,即使每次调用check_params()
时都会触发陷阱。RETURN上的陷阱是局部的东西,除非在它们上面显式设置了调试或跟踪标志,否则它们不会被其他函数继承,但看起来它们在运行之间保持设置。或者bash为它们保留了陷阱。当从特定函数调用check_params()时,应该只设置此陷阱,但如果不取消陷阱,则它将继续在每次check_a_param()
返回大于零的值时触发,而与FUNCNAME[1]
中的内容无关。
在这里,我放弃了,因为我现在看到的唯一出口是在check_params()
中的每个|| return $?
之前实现对调用函数的检查。但这太丑陋了,它伤害了我的眼睛。
我只能补充一点,即在设置陷阱的行中的$?
将始终返回0。因此,如果例如在return_trap()
中声明一个local
变量retval
,并放置检查该变量的代码
trap "return_trap; [ -v retval ]; echo $?; trap - RETURN; return $retval" RETURN
无论
retval
是否被设置,它都会打印0。但是如果您使用trap "return_trap; [ -v retval ] && echo set || echo unset; trap - RETURN; return $retval" RETURN
它会打印“未设置”。
GNU bash,版本4.3.39(1)-release(x86_64-pc-linux-gnu)
trap "..."
,而不是trap '...'
?乍一看,这让我觉得$?
在定义时被评估,而不是在调用时,因为引号的问题。 - Charles Duffy