Raise exception on shell command failure? - ruby

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?

Ruby 2.6 adds an exception: argument:
system('ctat nonexistent.txt', exception: true) # Errno::ENOENT (No such file or directory - ctat)

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.

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.

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

Related

fish shell evaluate make return code

I'm trying to write a script in Fish that runs a Make recipe and then executes all of the resultant binaries. The problem I'm having is that I would like to have the script exit with an error code if the make command encounters an error. Whenever I try to capture Make's return value, I end up with its output log instead.
For example:
if test (make allUnitTests) -eq 0
echo "success"
else
echo "fail"
end
returns an error because "test" is seeing the build process, not the terminating condition.
I wrote the script so that I could easily make Jenkins run all my unit tests whenever I trigger a build. Since I haven't been able to get the above section of the script working correctly, I've instead instructed Jenkins to run the make command as a separate command, which does exactly what I want: halting the entire build process without executing any binaries if anything fails to compile. Thus, at this point my question is more of an academic exercise, but I would like to add building the unit test binaries into the script (and have it cleanly terminate on a build error) for the benefit of any humans who might check out the code and would like to run the unit tests.
I played a little with something like:
if test (count (make allUnitTests | grep "Stop")) -eq 0
but this has two problems:
I'm apparently piping stdout when I need to pipe stderr. (Come to think of it, if I could just check to see if anything was written to stderr, then I wouldn't need grep at all.)
Grep is swallowing all the log data piped to it, which I really want to be visible on the console.
You are misunderstanding the parentheses - these run a command substitution. What this does is capture the output of the process running in the substitution, which it will then use as arguments (separated by newlines by default) to the process outside.
This means your test will receive the full output of make.
What you instead want to do is just run if make allUnitTests without any parens, since you are just interested in the return value.
If you would like to do something between running make and checking its return value, the "$status" variable always contains the return value of the last command, so you can save that:
make allUnitTests
set -l makestatus $status
# Do something else
if test $makestatus -eq 0
# Do the if-thing
else
# Do the else-thing
end

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

In a Ruby script, there are various ways to call system commands / command lines
backticks: `command arg1 arg2`
delimited form, e.g. %x(command arg1 arg2) (other delimiters available)
Kernel#system method: system('command arg1 arg2')
Kernel#exec method: exec('command arg1 arg2')
If I want the Ruby script to fail (with an exception) when a called command fails (with a non-zero exit code) I can either check for the exit code in the special variable $? for the first two variants:
`command arg1 arg2`
fail unless $? == 0
or
%x,command arg1 arg2,
fail unless $? == 0
If I'm fine with the command's standard output going to the Ruby script's standard output (and I am), I can use variant 3 and check its return value:
unless system('command arg1 arg2')
fail
end
If I don't care about the ability to rescue the exception nor about the stacktrace printing behavior of unrescued exceptions, I can of course use exit(1) or in the first two variants exit($?) in place of fail.
If further the execution of the command is the last thing the Ruby script should do, even when the command succeeds (exit code 0), I can use the fourth variant:
exec('command arg1 arg2')
This will replace the Ruby process with the new process created by invoking the command, but the effect for the caller of the Ruby script will be the same: He sees a non-zero exit code exactly if the called command caused a non-zero exit code.
I very much like the conciseness of the fourth variant, but if executing the command isn't the last thing to do in case it succeeds, I can unfortunately not use it. The conditional fail or exit invocations of the other variants look very unclean in comparison and in one usecase of mine more often than not violate the single level of abstraction and single responsibility principles.
I could of course easily write a wrapper function for any of the first three approaches to make their usage look just as concise, but as this seems like such a fundamental modus operandi, I wondered whether Ruby already has something like that built in ... be it a utility function I could use instead of a wrapper of my own, or a mechanism that changes the behavior of one or several of the command invocation methods to cause an error or a non-zero exit when a command fails, analogous to sh's and bash's option to set -e.
As far as I know there is no built-in way to do this, but you can almost get the behavior you want with a little metaprogramming magic
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
This works by redefining system for Object, while explicitly using Kernel.system when the built-in behavior is needed.

Is it good practice to use sys.exit(0) at the end of a Python script?

I'm learning to write Python 3 scripts to use in a shell. I have a textbook which seems to say that such a script should always use sys.exit(0) to end the script and return the code 0. Is that really necessary?
For example, suppose I run the following script with python3 foo.py:
import sys
sys.stdout.write('Hello world\n')
If I then do echo $? the response is '0'. So, the script did exit with code 0, and adding sys.exit(0) as the last line would have been redundant.
Should I leave it out, or is it still good practice?
There is no need to use sys.exit(0) at the end of your script if no problems have been found, and it is not good practice to do so.
If you need to exit early with an error, and you don't care about the exact non-zero exit code, you can use:
raise SystemExit('error message here')
The message will be printed and return code will be 1. Otherwise, print() the message and use:
sys.exit(returncode)
to get a different returncode.

Testing the $? global variable in a ruby rspec

I have a piece of code like:
output = `shell-command-to-run`
unless $?.success?
raise "Failure running shell command!"
end
I've mocked the backtick method to prevent the external shell command from running during my specs, but I've not found a way to set the $? global variable to exercise the failure side of the spec.
Maybe you should have a script that returns the sorts of errors you're expecting to uncover so you can test a variety of conditions. For example:
#!/usr/bin/env ruby
exit(ARGV[0].to_i)
You can then pass in the exit code you want to trap.
Instead of mocking the call, you just run a different script entirely to validate it handles errors correctly.

Exiting a program in ruby

I'm writing some code in ruby, and I want to test for the presence of a command before the launch of the program. If the command isn't installed, I want to display an error message and quit the program. So right now, I'm doing this.
puts `type -P spark &>/dev/null && continue || { echo "You must install spark"; exit 0; } `
So, everything works fine, BUT, the "exit 0" isn't, and I can't figure out why.
Do you have any idea to fix this? Or even better, is there another way to do it?
The reason you're not exiting your script is that the call to exit is within the backticks. It's exiting the subshell called to run spark, but that's not the process interpreting your ruby script.
You could check the contents of the $? variable, which returns Process:Status for the backtick command after the command has been run.
As Daniel Pittman has suggested, however, it would be easier to check that the executable was available using something like FileTest. However, you probably want to couple that with a test of the return value, in case some other, more complex, failure occurs.
The much better way to do that is:
ENV["PATH"].split(':').any? {|x| FileTest.executable? "#{x}/spark" }
Season to taste for getting the full path, or using File.join to build the path, or platform path separators, or whatever.

Resources