dgo*_*o.a 15 bash command-substitution
根据这个参考手册:
-E(还有 -o errtrace)
如果设置,则 ERR 上的任何陷阱都由 shell 函数、命令替换和在子 shell 环境中执行的命令继承。在这种情况下,ERR 陷阱通常不会被继承。
但是,我必须错误地解释它,因为以下不起作用:
#!/usr/bin/env bash
# -*- bash -*-
set -e -o pipefail -o errtrace -o functrace
function boom {
echo "err status: $?"
exit $?
}
trap boom ERR
echo $( made up name )
echo " ! should not be reached ! "
Run Code Online (Sandbox Code Playgroud)
我已经知道简单的赋值,my_var=$(made_up_name), 将退出脚本set -e(即 errexit)。
是 -E/-o errtrace应该的工作方式类似于上面的代码?或者,最有可能的是,我误读了它?
注意:zsh如果您不将其配置为接受此处大多数示例的“内联注释”并且不像我对sh <<-\CMD.
好的,所以,正如我在上面的评论中所述,我并不特别了解bash 的set -E,但我知道 POSIX 兼容的 shell 提供了一种简单的方法来测试一个值,如果你愿意的话:
sh -evx <<-\CMD
_test() { echo $( ${empty:?error string} ) &&\
echo "echo still works"
}
_test && echo "_test doesnt fail"
# END
CMD
sh: line 1: empty: error string
+ echo
+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail
Run Code Online (Sandbox Code Playgroud)
在上面你会看到,虽然我曾经parameter expansion测试过${empty?} _test()仍然 return是一个通行证——正如上次所证明的那样。echo发生这种情况是因为失败的值杀死了$( command substitution )包含它的子外壳,但它的父外壳——_test此时——继续运输。而echo并不关心-这是很多快乐只投放一个\newline; echo是不是一个考验。
但是考虑一下:
sh -evx <<-\CMD
_test() { echo $( ${empty:?error string} ) &&\
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
_test ||\
echo "this doesnt even print"
# END
CMD
_test+ sh: line 1: empty: function doesnt run
Run Code Online (Sandbox Code Playgroud)
因为我_test()'s在INIT here-document现在输入了一个预先评估的参数,该_test()函数甚至根本没有尝试运行。更重要的是,sh外壳显然完全放弃了鬼魂,echo "this doesnt even print" 甚至不打印。
可能这不是你想要的。
发生这种情况是因为${var?}样式参数扩展旨在shell在缺少参数的情况下退出,它的工作方式如下:
${parameter:?[word]}如果
Null或Unset.如果参数未设置或为空,则指示错误expansion of word(或如果省略单词则指示未设置的消息)应为written to standard error和shell exits with a non-zero exit status。否则, 的值parameter shall be substituted。交互式 shell 不需要退出。
我不会复制/粘贴整个文档,但如果您希望某个set but null值失败,请使用以下表单:
${var:?error message }
与:colon如上述。如果您希望某个null值成功,只需省略冒号。您也可以否定它并仅在设置值时失败,我稍后将展示。
另一个运行 _test():
sh <<-\CMD
_test() { echo $( ${empty:?error string} ) &&\
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
echo "this runs" |\
( _test ; echo "this doesnt" ) ||\
echo "now it prints"
# END
CMD
this runs
sh: line 1: empty: function doesnt run
now it prints
Run Code Online (Sandbox Code Playgroud)
这适用于各种快速测试,但在上面你会看到_test(),从pipeline失败的中间运行,实际上它包含的command list子shell完全失败,因为函数中的任何命令都没有运行,也没有echo运行以下命令,尽管它还表明它可以很容易地进行测试,因为echo "now it prints" 现在可以打印。
我猜,魔鬼在细节中。在上面的例子中,退出的 shell不是脚本的,_main | logic | pipeline而是( subshell in which we ${test?} ) ||需要一些沙箱的。
这可能并不明显,但如果您只想通过相反的情况或仅传递set=值,它也相当简单:
sh <<-\CMD
N= #N is NULL
_test=$N #_test is also NULL and
v="something you would rather do without"
( #this subshell dies
echo "v is ${v+set}: and its value is ${v:+not NULL}"
echo "So this ${_test:-"\$_test:="} will equal ${_test:="$v"}"
${_test:+${N:?so you test for it with a little nesting}}
echo "sure wish we could do some other things"
)
( #this subshell does some other things
unset v #to ensure it is definitely unset
echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
echo "So this ${_test:-"\$_test:="} will equal NULL ${_test:="$v"}"
${_test:+${N:?is never substituted}}
echo "so now we can do some other things"
)
#and even though we set _test and unset v in the subshell
echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
# END
CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without
Run Code Online (Sandbox Code Playgroud)
上面的例子中利用的所有4所形成POSIX的参数替换和它们的各种:colon null或not null测试。上面的链接中有更多信息,这里又是。
我想我们也应该展示我们的_test功能,对吧?我们只是声明empty=something为我们函数的参数(或任何时间之前):
sh <<-\CMD
_test() { echo $( echo ${empty:?error string} ) &&\
echo "echo still works" ; } 2<<-INIT
${empty?tested as a pass before function runs}
INIT
echo "this runs" >&2 |\
( empty=not_empty _test ; echo "yay! I print now!" ) ||\
echo "suspiciously quiet"
# END
CMD
this runs
not_empty
echo still works
yay! I print now!
Run Code Online (Sandbox Code Playgroud)
应该注意的是,这个评估是独立的——它不需要额外的测试来失败。再举几个例子:
sh <<-\CMD
empty=
${empty?null, no colon, no failure}
unset empty
echo "${empty?this is stderr} this is not"
# END
CMD
sh: line 3: empty: this is stderr
sh <<-\CMD
_input_fn() { set -- "$@" #redundant
echo ${*?WHERES MY DATA?}
#echo is not necessary though
shift #sure hope we have more than $1 parameter
: ${*?WHERES MY DATA?} #: do nothing, gracefully
}
_input_fn heres some stuff
_input_fn one #here
# shell dies - third try doesnt run
_input_fn you there?
# END
CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?
Run Code Online (Sandbox Code Playgroud)
最后我们回到最初的问题:如何处理$(command substitution)子shell中的错误?事实是 - 有两种方式,但都不是直接的。问题的核心是shell的评估过程- shell扩展(包括$(command substitution))在外壳的评估过程不会比当前的shell命令执行较早发生-这是当你的错误,可以被捕集。
op 遇到的问题是,当当前 shell 评估错误时,$(command substitution)子 shell 已经被替换掉了 - 没有错误存在。
那么这两种方式是什么?要么$(command substitution)像没有它一样在子 shell 中使用测试显式执行它,要么将其结果吸收到当前的 shell 变量中并测试它的值。
echo "$(madeup && echo \: || echo '${fail:?die}')" |\
. /dev/stdin
sh: command not found: madeup
/dev/stdin:1: fail: die
echo $?
126
Run Code Online (Sandbox Code Playgroud)
var="$(madeup)" ; echo "${var:?die} still not stderr"
sh: command not found: madeup
sh: var: die
echo $?
1
Run Code Online (Sandbox Code Playgroud)
无论每行声明的变量数量如何,这都会失败:
v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"
sh: command not found: madeup
sh: v1: parameter not set
Run Code Online (Sandbox Code Playgroud)
我们的返回值保持不变:
echo $?
1
Run Code Online (Sandbox Code Playgroud)
trap 'printf %s\\n trap resurrects shell!' ERR
v1="$(madeup)" v2="$(printf %s\\n shown after trap)"
echo "${v1:?#1 - still stderr}" "${v2:?invisible}"
sh: command not found: madeup
sh: v1: #1 - still stderr
trap
resurrects
shell!
shown
after
trap
echo $?
0
Run Code Online (Sandbox Code Playgroud)
如果设置,则 ERR 上的任何陷阱都会由 shell 函数、命令替换和在子 shell 环境中执行的命令继承
在你的脚本中它是一个命令执行(echo $( made up name ))。在 bash 命令中,用 ; 分隔;或换行。在命令中
echo $( made up name )
Run Code Online (Sandbox Code Playgroud)
$( made up name )被视为命令的一部分。即使这部分失败并返回错误,整个命令也会成功执行,因为echo不知道这一点。由于命令返回 0,因此没有触发陷阱。
你需要把它放在两个命令中,赋值和echo
var=$(made_up_name)
echo $var
Run Code Online (Sandbox Code Playgroud)