使用“ -o errtrace”(即,设置-E)捕获命令替换中的错误


14

根据此参考手册

-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 ! "

我已经知道简单的赋值,my_var=$(made_up_name)将使用set -e(即errexit)退出脚本。

-E/-o errtrace应该的工作方式类似于上面的代码?或者,很可能是我误读了吗?


2
这是一个很好的问题。更换echo $( made up name )$( made up name )产生所需的行为。我没有解释。
iruvar

我不了解bash的-E,但我确实知道-e仅在错误是由管道中的最后一条命令导致的情况下才影响shell退出。因此,您的var=$( pipe )$( pipe )示例都将代表管道端点,而pipe > echo不会。我的手册页说:“ 1。多命令管道中的任何单个命令的故障都不会导致外壳退出。仅应考虑管道本身的故障。”
mikeserv 2014年

但是,您可以使它失败:echo $($ {madeupname?})。但这是-e。同样,-E在我自己的经验之外。
mikeserv

@mikeserv @ 1_CR bash手册@ echo表示echo总是返回0。这必须在分析中加​​以考虑……

Answers:


5

注意:zsh如果您在此处的大多数示例未将其配置为接受“内联注释”,并且没有像我一样通过代理shell运行它们,则会抱怨“错误模式” 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

上面你会看到,虽然我曾经parameter expansion测试${empty?} _test()仍然 returnš一通-作为明证是,在过去echo这是因为失败的价值杀死$( command substitution )包含它的子shell,但它的母贝- _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

因为我现在在_test()'s输入中输入了预先评估的参数,所以INIT here-document_test()函数甚至根本不会尝试运行。而且,sh外壳显然完全放弃了鬼影,echo "this doesnt even print" 甚至没有打印出来。

可能不是您想要的。

发生这种情况是因为${var?}样式参数扩展旨在shell在缺少参数的情况下退出,其工作方式如下

${parameter:?[word]}

指示Error if NullUnset.If参数未设置或为null,expansion of word(或如果省略word则指示未设置的消息)应为written to standard errorshell exits with a non-zero exit status否则,的值parameter shall be substituted交互式外壳程序无需退出。

我不会复制/粘贴整个文档,但是如果您想输入一个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

这适用于各种快速测试,但在上面您会看到_test(),从pipeline失败的中间开始运行,实际上,它包含的command list子外壳完全失败了,因为该函数中的任何命令都不会运行,也不会随后的echo运行,尽管它也显示可以轻松测试,因为echo "now it prints" 现在可以打印。

我想魔鬼在细节上。在上述情况下,退出的外壳不是脚本的外壳,_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

上面的例子中利用的所有4所形成POSIX的参数替换和它们的各种:colon nullnot 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!

应当指出,此评估是独立的-不需要其他测试即可通过。还有更多示例:

    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?

最后,我们回到最初的问题:如何在$(command substitution)子Shell中处理错误?事实是-有两种方法,但都不是直接的。问题的核心是外壳的评估过程-外壳扩展(包括$(command substitution))在shell的评估过程中发生的时间比当前shell命令的执行时间更早-在这种情况下,错误可能会被捕获和捕获。

op遇到的问题是,到当前外壳评估错误时,$(command substitution)子外壳已被替换掉-没有错误。

那么这两种方式是什么?可以$(command substitution)像没有它一样在子shell中使用测试显式地执行它,或者将其结果吸收到当前的shell变量中并测试其值。

方法1:

    echo "$(madeup && echo \: || echo '${fail:?die}')" |\
          . /dev/stdin

sh: command not found: madeup
/dev/stdin:1: fail: die

    echo $?

126

方法2:

    var="$(madeup)" ; echo "${var:?die} still not stderr"

sh: command not found: madeup
sh: var: die

    echo $?

1

无论每行声明多少变量,此操作都会失败:

   v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"

sh: command not found: madeup
sh: v1: parameter not set

我们的返回值保持不变:

    echo $?
1

现在的陷阱:

    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

例如,实际上是他的确切规范:echo $ {v = $(madeupname)}; 回声“未达到$ {v:?trap1st} !!”
mikeserv

1
总结一下:这些参数扩展是控制是否设置变量等的好方法。关于echo的退出状态,我注意到它echo "abc" "${v1:?}"似乎没有执行(从不打印abc)。Shell返回1。无论是否有命令("${v1:?}"直接在cli上),都是如此。但是对于OP的脚本,触发陷阱所需要做的只是将他的变量赋值包含一个替换不存在的命令的变量放在一行上。否则,echo的行为将始终返回0,除非像您解释的测试那样被打断。

只是v=$( madeup )。我看不出有什么不安全的地方。这只是一项任务,例如,该人拼错了命令v="$(lss)"。错误。是的,您可以使用最后一条命令$?的错误状态进行验证。-因为它是行上的命令(没有命令名称的赋值),没有别的任何内容-不是要回显的参数。另外,在这里它被函数=!= 0捕获,因此您将获得两次反馈。否则,可以肯定的是,正如您所解释的,在框架中有一种更好的方法来有序地执行此操作,但是OP只有1行:回声加上他的替换失败。他想知道回声。

是的,在问题中提到了简单的分配,这是理所当然的,尽管我无法提供有关如何使用-E的具体建议,但我确实试图显示出与所证明的问题类似的结果,而没有诉诸于此。洗礼。无论如何,正是您提到的问题(如每行一次分配)使此类解决方案难以在管道中处理,我也演示了如何处理。尽管您说的是真的- 简单分配它并没有不安全之处。
mikeserv 2014年

1
好点-也许需要更多的努力。我想想。
mikeserv

1

如果设置,则ERR函数上的任何陷阱都将被shell函数,命令替换以及在子shell环境中执行的命令继承

在脚本中,它是命令执行(echo $( made up name ))。在bash中,命令之间用或分隔换行。在命令中

echo $( made up name )

$( made up name )被视为命令的一部分。即使这部分失败并返回错误,echo也不知道整个命令是否成功执行。当命令返回0时,不会触发陷阱。

您需要将其放在两个命令中,赋值和回显

var=$(made_up_name)
echo $var

echo $var不会失败- $varecho实际查看之前会扩展为空。
mikeserv

0

这是由于bash中的错误所致。在命令替换步骤中

echo $( made up name )

made在子外壳程序中运行(或找不到),但是以某种方式“优化”了子外壳程序,使其不使用父外壳程序中的某些陷阱。在版本4.4.5中已修复此问题:

在某些情况下,优化了一个简单的命令以消除分叉,从而导致未执行EXIT陷阱。

对于bash 4.4.5或更高版本,您应该看到以下输出:

error.sh: line 13: made: command not found
err status: 127
  ! should not be reached !

已按预期方式调用了陷阱处理程序,然后退出了子外壳程序。(set -e仅导致退出子shell,而不导致父shell退出,因此实际上应该到达“不应到达”消息。)

较旧版本的一种解决方法是强制创建完整的,未经优化的子外壳程序:

echo $( ( made up name ) )

需要额外的空格以区别算术扩展。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.