当shelled-out命令返回非零退出代码时,如何让Ruby脚本失败?

How to get Ruby script to fail when shelled-out command returns with non-zero exit code?

在Ruby脚本中,有多种方法可以调用系统命令/命令行

  • 背景:`command arg1 arg2`
  • 定界形式,如%x(command arg1 arg2)(可提供其他定界符)
  • Kernel#system法:system('command arg1 arg2')
  • Kernel#exec法:exec('command arg1 arg2')
  • 如果我希望ruby脚本在被调用的命令失败(使用非零退出代码)时失败(有例外),我可以检查特殊变量$?中前两个变量的退出代码:

    1
    2
    `command arg1 arg2`
    fail unless $? == 0

    1
    2
    %x,command arg1 arg2,
    fail unless $? == 0

    如果命令的标准输出指向ruby脚本的标准输出(我也可以),那么我可以使用变量3并检查其返回值:

    1
    2
    3
    unless system('command arg1 arg2')
      fail
    end

    如果我不关心挽救异常的能力,也不关心未提示异常的stacktrace打印行为,我当然可以使用exit(1)或前两个变体exit($?)代替fail

    如果命令的进一步执行是Ruby脚本应该做的最后一件事,即使命令成功(退出代码0时),我也可以使用第四个变量:

    1
    exec('command arg1 arg2')

    这将用调用命令创建的新进程替换Ruby进程,但Ruby脚本调用方的效果将是相同的:如果被调用的命令导致非零退出代码,他将看到一个非零退出代码。

    我非常喜欢第四个变量的简洁性,但是如果执行命令不是万一成功的最后一件事,那么不幸的是我不能使用它。相比之下,其他变体的有条件的failexit调用看起来非常不干净,在我的一个用例中,经常违反单一抽象级别和单一责任原则。

    当然,我可以很容易地为前三种方法中的任何一种编写一个包装函数,使它们的用法看起来同样简洁,但由于这似乎是一种基本的操作方式,我想知道Ruby是否已经有了类似的内置功能…无论是我可以使用的实用函数,而不是我自己的包装器,还是在命令失败时改变一个或多个命令调用方法的行为以导致错误或非零退出的机制,类似于sh和bash对EDOCX1的选项(13)。


    据我所知,没有内置的方法可以做到这一点,但是你几乎可以用一点元编程的魔力得到你想要的行为。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    def set_exit enable
      if enable
        define_method :system do |*args|
          Kernel.system *args
          exit $?.exitstatus unless $?.success?
        end
      else
        define_method :system, Kernel.instance_method(:system)
      end
    end

    set_exit true
    # ...
    # any failed system calls here will cause your script to exit
    # ...
    set_exit false
    # now system is back to normal

    这是通过为Object重新定义system,而在需要内置行为时显式使用Kernel.system