【问题标题】:Writing try catch finally in shell最后在shell中写try catch
【发布时间】:2013-03-17 09:28:37
【问题描述】:

有没有像 java try catch finally 这样的 linux bash 命令? 还是 linux shell 一直在运行?

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

【问题讨论】:

标签: shell syntax try-catch finally


【解决方案1】:

警告:退出陷阱不会始终执行。自从写了这个答案后,我遇到了一些情况,我的退出陷阱不会被执行,导致文件丢失,原因我还没找到。

当我使用 Ctrl+C 停止一个 python 脚本时出现了问题,该脚本又使用退出陷阱执行了一个 bash 脚本——这实际上应该导致退出陷阱被执行,因为退出陷阱是在 SIGINT 上执行的在 bash 中。

因此,虽然trap .. exit 对清理很有用,但仍有一些情况不会执行,最明显的情况是断电和接收SIGKILL


当我添加其他选项或以其他方式更改它们时,我经常会导致 bash 脚本变得非常大。当 bash 脚本包含大量函数时,使用“trap EXIT”可能会变得很重要。

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

dotask TASK [ARG ...]

每个TASK 可能包含子步骤,需要在其中执行清理。

在这种情况下,使用子shell 来生成范围退出陷阱会很有帮助,例如

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

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

此外,编写单个退出陷阱通常很不方便。例如,清理步骤可能取决于函数在遇到错误之前走了多远。如果能够进行 RAII 样式的清理声明,那就太好了:

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

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

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

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

[multiple bash traps for the same signal] 中讨论的解决方案,使用来自 trap -p EXIT 的输出修补陷阱同样会失败:即使子 shell 不继承 EXIT 陷阱,trap -p exit 将显示父 shell 的处理程序,所以,再次,需要手动重置。

【讨论】:

    【解决方案2】:

    我发现我的脚本使用这种语法是成功的:

    # 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 语句退出返回错误,或者因为有“exit 1”命令,解释器将继续执行“catch”,然后是 finally 语句。但是,如果 try 语句使用“exit”命令退出或只是成功完成,则解释器将继续执行 finally 语句而不运行 catch 语句。
    • 我用示例更新了我的答案,这些示例显示了如何根据程序流程的要求在各种情况下处理错误。
    【解决方案3】:

    另一种方法是:

    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 
    

    【讨论】:

      【解决方案4】:

      根据您的示例,无论脚本如何退出,您似乎都在尝试执行类似于始终删除临时文件的操作。在 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
      

      trap 中的rm tmp 语句总是在脚本退出时执行,因此文件“tmp”总是会尝试删除。

      已安装的陷阱也可以重置;仅使用信号名称调用 trap 将重置信号处理程序。

      trap EXIT
      

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

      【讨论】:

      • 使用 'trap' 的一个好处是,除了 EXIT 之外,您还可以捕获其他信号。特别是,您可以捕获 SIGINT (Control-C)。为此,只需将其添加到陷阱语句的末尾即可。例如trap 'rm tmp' EXIT SIGINT
      • 从快速测试看来,EXIT 处理程序调用了SIGINTSIGTERM
      • 这肯定是最干净的方法。
      • 好答案;值得指出的是,陷阱的执行不会影响脚本的退出状态——这比 if/else|| 方法具有优势,更像是 C++ 或 Java try/catch
      • 扩展上述 cmets:在我的测试中,Control-C 导致 SIGINT 首先触发,然后是 EXIT。因此,出于此处讨论的目的,捕获 SIGINT 是多余的,实际上捕获两者会导致您的清理命令被执行两次——很可能不是您想要的。另一方面,捕获 SIGTERM 会导致程序在收到 kill 时不会终止。 kill -9 会跳过所有三个陷阱。因此,在这种情况下也不建议捕获 SIGTERM。 (如果您出于某种原因决定捕获 SIGTERM,您可能希望在您的陷阱处理程序中调用 'exit'。)
      【解决方案5】:

      mv 有两个参数,所以你可能真的想 cat 输出文件的内容:

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

      【讨论】:

        【解决方案6】:

        嗯,有点:

        { # your 'try' block
            executeCommandWhichCanFail &&
            mv output
        } || { # your 'catch' block
            mv log
        }
        
         rm tmp # finally: this will always happen
        

        【讨论】:

        • 请注意,您必须在executeCommandWhichCanFail 之后使用&&,否则会盲目进行。即使你在它之前使用set -e(我不明白)。
        • 简洁明了。但我更喜欢使用trap,因为|| 并不能确保即使在异常条件(信号)下也能执行另一部分,这几乎是人们对finally 的期望。
        • 你可以使用(...)而不是{...}然后你就不必在每一行都使用&&
        猜你喜欢
        • 2013-09-10
        • 1970-01-01
        • 2021-01-15
        • 1970-01-01
        • 2022-09-24
        • 2014-04-17
        • 2013-08-27
        • 2012-11-29
        相关资源
        最近更新 更多