最终在shell中编写try catch


70

是否有像Java try catch这样的linux bash命令?还是linux shell总是会继续运行?

try {
   `executeCommandWhichCanFail`
   mv output
} catch {
    mv log
} finally {
    rm tmp
}



@KugathasanAbimaran必须使用关键字错误处理而不是最后尝试catch,谢谢您的链接!
Jetse 2013年

Answers:


102

好吧,有点:

{ # your 'try' block
    executeCommandWhichCanFail &&
    mv output
} || { # your 'catch' block
    mv log
}

 rm tmp # finally: this will always happen

4
请注意,您必须使用 &&之后,executeCommandWhichCanFail否则会盲目进行。即使您先使用它set -e(我也不知道)。
AJP 2014年

5
简短明了。但是我更喜欢使用trap,因为||即使在特殊条件(信号)下也不能确保另一部分得到执行,这几乎是人们期望的finally
2015年

您可以使用(...)代替,{...}然后不必在每行上使用&&
Alexander Mills

111

根据您的示例,无论脚本如何退出,您似乎都在尝试执行类似于始终删除临时文件的操作。在Bash中,请尝试使用trap内置命令捕获EXIT信号。

#!/bin/bash

trap 'rm tmp' EXIT

if executeCommandWhichCanFail; then
    mv output
else
    mv log
    exit 1 #Exit with failure
fi

exit 0 #Exit with success

脚本退出时始终执行中的rm tmp语句trap,因此将始终尝试删除文件“ tmp”。

安装的疏水阀也可以复位;仅使用信号名称的trap调用将重置信号处理程序。

trap EXIT

有关更多详细信息,请参见bash手册页: man bash


2
使用“陷阱”的好处是,您还可以捕获EXIT以外的其他信号。特别是,您可以捕获SIGINT(Control-C)。为此,只需将其添加到trap语句的末尾即可。例如trap 'rm tmp' EXIT SIGINT
ishmael 2014年

7
从快速测试似乎EXIT处理程序呼吁SIGINTSIGTERM
Cuadue

2
这肯定是最干净的方法。
切斯特布里

4
好答案; 可能值得指出的是,陷阱的执行不会影响脚本的退出状态-这是C ++或Java /的优势if/else||方法。trycatch
Toby Speight

1
扩展上面的评论:在我的测试中,Control-C使SIGINT首先触发,然后退出。因此,出于此处讨论的目的,捕获SIGINT是多余的,并且实际上捕获两者都会导致您的清除命令执行两次-很有可能不是您想要的。另一方面,诱捕SIGTERM导致程序在终止时不会终止。杀死-9会跳过所有三个陷阱。因此,在这种情况下也不建议捕获SIGTERM。(如果您由于某种原因决定捕获SIGTERM,则可能要在陷阱处理程序中调用“退出”。)
benkc

1

mv 需要两个参数,所以您可能真的想照顾输出文件的内容:

echo `{ execCommand && cat output ; } || cat log`
rm -f tmp

1

我使用以下语法在脚本中找到了成功:

# Try, catch, finally
(echo "try this") && (echo "and this") || echo "this is the catch statement!"

# this is the 'finally' statement
echo "finally this"

如果try语句抛出错误或以结束exit 1,则解释器将继续执行catch语句,然后再执行finally语句。

如果两个try语句都成功(和/或以结尾exit),则解释器将跳过catch语句,然后运行finally语句。

范例_1:

goodFunction1(){
  # this function works great
  echo "success1"
}

goodFunction2(){
  # this function works great
  echo "success2"
  exit
}

(goodFunction1) && (goodFunction2) || echo "Oops, that didn't work!"

echo "Now this happens!"

输出_1

success1
success2
Now this happens!

范例_2

functionThrowsErr(){
  # this function returns an error
  ech "halp meh"
}

goodFunction2(){
  # this function works great
  echo "success2"
  exit
}

(functionThrowsErr) && (goodFunction2) || echo "Oops, that didn't work!"

echo "Now this happens!"

输出_2

main.sh: line 3: ech: command not found
Oops, that didn't work!
Now this happens!

范例_3

functionThrowsErr(){
  # this function returns an error
  echo "halp meh"
  exit 1
}

goodFunction2(){
  # this function works great
  echo "success2"
}

(functionThrowsErr) && (goodFunction2) || echo "Oops, that didn't work!"

echo "Now this happens!"

输出_3

halp meh
Oops, that didn't work!
Now this happens!

请注意,功能的顺序将影响输出。如果您需要分别尝试和捕获这两个语句,请使用两个try catch语句。

(functionThrowsErr) || echo "Oops, functionThrowsErr didn't work!"
(goodFunction2) || echo "Oops, good function is bad"

echo "Now this happens!"

输出量

halp meh
Oops, functionThrowsErr didn't work!
success2
Now this happens!

如果这些“ try语句”之一引发错误(抛出异常)怎么办?它如何处理错误(异常)?
faza

如果其中一个try语句退出返回错误,或者由于存在“ exit 1”命令,则解释器将继续执行“ catch”,然后执行finally语句。但是,如果try语句通过“ exit”命令退出或仅通过成功完成而退出,则解释器将继续执行finally语句,而无需运行catch语句。
DogeCode

我用示例更新了我的答案,这些示例显示了根据程序流要求在各种情况下如何处理错误。
DogeCode

0

另一种方法是:

set -e;  # stop on errors

mkdir -p "$HOME/tmp/whatevs"

exit_code=0

(
  set +e;
  (
    set -e;
    echo 'foo'
    echo 'bar'
    echo 'biz'
  )
  exit_code="$?"
)

rm -rf "$HOME/tmp/whatevs"

if [[ "exit_code" != '0' ]]; then
   echo 'failed';
fi 

尽管以上内容并未真正带来任何好处:

set -e;  # stop on errors

mkdir -p "$HOME/tmp/whatevs"

exit_code=0

(
    set -e;
    echo 'foo'
    echo 'bar'
    echo 'biz'
    exit 44;
    exit 43;

) || {
   exit_code="$?"  # exit code of last command which is 44
}

rm -rf "$HOME/tmp/whatevs"

if [[ "exit_code" != '0' ]]; then
   echo 'failed';
fi 

0

当我添加其他选项或以其他方式更改它们时,我常常会导致bash脚本变得很大。当bash脚本包含许多功能时,使用'trap EXIT'可能会变得很简单。

例如,考虑一个脚本被调用为

dotask TASK [ARG ...]

其中每个TASK子步骤都可以包含子步骤,因此需要在它们之间进行清理。

在这种情况下,使用子外壳来产生作用域出口陷阱很有帮助,例如

function subTask (
    local tempFile=$(mktemp)
    trap "rm '${tempFile}'" exit
    ...
)

但是,使用子外壳可能很棘手,因为它们无法设置父外壳的全局变量。

另外,编写单个出口陷阱通常很不方便。例如,清理步骤可能取决于函数在遇到错误之前走了多远。能够进行RAII样式清理声明将是一件很不错的事情:

function subTask (
    ...
    onExit 'rm tmp.1'
    ...
    onExit 'rm tmp.2'
    ...
)

使用类似的东西似乎很明显

handlers=""
function onExit { handlers+="$1;"; trap "$handlers" exit; }

更新陷阱。但这对于嵌套子shell失败,因为这会导致父shell的处理程序过早执行。客户端代码将必须handlers在子Shell的开头显式重置变量。

[针对同一信号的多个bash陷阱]中讨论的解决方案,通过使用来自的输出来修补陷阱,trap -p EXIT同样会失败:即使子外壳程序不继承EXIT陷阱,trap -p exit也会显示父外壳程序的处理程序,因此,同样需要手动重置。

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.