8

I'm writing some scripts in Ruby, and I need to interface with some non-Ruby code via shell commands. I know there are at least 6 different ways of executing shell commands from Ruby, unfortunately, none of these seem to stop execution when a shell command fails.

Basically, I'm looking for something that does the equivalent of:

set -o errexit

...in a Bash script. Ideally, the solution would raise an exception when the command fails (i.e., by checking for a non-zero return value), maybe with stderr as a message. This wouldn't be too hard to write, but it seems like this should exist already. Is there an option that I'm just not finding?

Benjamin Oakes
  • 12,262
  • 12
  • 65
  • 83

4 Answers4

12

Ruby 2.6 adds an exception: argument:

system('ctat nonexistent.txt', exception: true) # Errno::ENOENT (No such file or directory - ctat)
Benjamin Oakes
  • 12,262
  • 12
  • 65
  • 83
9

You can use one of ruby's special variables. The $? (analogous to the same shell script var).

`ls`
if $? == 0
  # Ok to go
else
  # Not ok
end

Almost every program sets this var to 0 if everything went fine.

jgillich
  • 71,459
  • 6
  • 57
  • 85
Tomás Senart
  • 1,403
  • 1
  • 12
  • 15
  • 6
    Why would you use `to_s` to compare with the string `0`? `if $? == 0` – glenn jackman Feb 09 '10 at 15:13
  • I would say even `if $?.zero?` – Nakilon May 12 '16 at 19:03
  • @Nakilon ```$?.zero?``` raise NoMethodError – Mu-ik Jeon Jun 14 '17 at 02:28
  • @Mu-ikJeon, you'll face that if you did not yet call any subprocess, because `$?` is `nil` from the beginning. – Nakilon Jun 14 '17 at 02:33
  • 1
    @Nakilon $? is Process::Status class. ```puts $?.class # Process::Status``` The class has no 'zero?' method. https://ruby-doc.org/core-2.2.0/Process/Status.html – Mu-ik Jeon Jun 15 '17 at 06:04
  • @Mu-ikJeon, oh you are right, weird. Seems like the error code is in `$?.exitstatus` https://stackoverflow.com/a/38683399/322020 – Nakilon Jun 15 '17 at 12:45
  • 2
    The best way to check is `if $?.success?` https://ruby-doc.org/core-2.2.0/Process/Status.html#method-i-success-3F. The way I use it is `exit $?.to_i unless $?.success?` – BvdBijl Oct 11 '17 at 16:10
  • $?.to_s returns "pid 908 exit 0" when I run it with system("command"), so this doesn't work. However, system("command") and also $?.success? both return true or false depending on whether there was an error. – Stan Nov 03 '20 at 17:51
9

Easiest way would be to create a new function (or redefine an existing one) to call system() and check the error code.

Something like:

old_sys = system

def system(...)
  old_system(...)
  if $? != 0 then raise :some_exception
end

This should do what you want.

user269044
  • 148
  • 5
  • 2
    Yeah, this was what I was thinking when I said "wouldn't be too hard to write", but it just seems like something that should have been solved already, you know? Thanks for the example. – Benjamin Oakes Feb 09 '10 at 15:00
  • 1
    Although, you'd want to `raise` instead of `throw` – glenn jackman Feb 09 '10 at 15:12
  • FYI: The readable alias for `$?` is `$CHILD_STATUS`. Refs: https://docs.ruby-lang.org/en/2.2.0/Process/Status.html, https://ruby-doc.org/stdlib-2.5.0/libdoc/English/rdoc/English.html – Ben Amos Oct 30 '20 at 20:56
8

Tiny bit simpler: you don't need to check $? w/ system, and since the command you ran will output to stderr itself you can usually just non-zero-exit rather than raising an exception w/ an ugly stack-trace:

system("<command>") || exit(1)

So you could take that a step further and do:

(system("<command 1>") &&
  system("<command 2>") &&
  system("<command 3>")) || exit(1)

...which would short-circuit and fail on error (in addition to being hard to read).

Ref: From Ruby 2.0 doc for system (although true of 1.8.7 as well):

system returns true if the command gives zero exit status, false for non zero exit status.

http://www.ruby-doc.org/core-2.0.0/Kernel.html#method-i-system

Ben Alavi
  • 392
  • 3
  • 7