I'm new to ruby, and I'm writing a pretty basic Rakefile. It contains a class Installer that has a method shell which calls sh like this:
class Installer
def shell(command)
sh command
end
end
When I run it, it throws
NoMethodError: undefined method `sh' for #<Installer:0x00007ffde8098b78>`
I have done some research to see what's wrong but most similar errors appear to be framework/library related, whereas my problem seems to be that it's just not possible to call sh from inside a method.
Is this the problem? if so, what options do I have (considering I want the program to crash if the command failed (which I believe system or backticks won't help here).
Change the sh invocation to Rake.sh. At least, that seemed to work for me.
The system method from the Kernel module returns false if the command run from the shell returns a non-zero value.
irb(main):002:0> system('exit 1')
=> false
irb(main):003:0> system('exit 0')
=> true
You can you use this as a way to exit on non-zero returns from your shell commands.
Related
Sort of an odd question, but: how would one go about creating a wrapper shell script that can be used in the #! line in other scripts.
wrap.sh
#!/bin/bash
set -e
echo "wrapper!"
exec ruby "$#"
test.rb
#!/usr/bin/env wrap.sh
puts RUBY_VERSION
puts "the ducks come from the trucks"
wrap.sh is in the path, and test.rb is marked as executable.
Now I do:
./test.rb
wrapper!
ruby: no Ruby script found in input (LoadError)
The goal is to execute the ruby script via the wrapper (The ruby version can be either local or comes from a traveling ruby install that is shipped along with the app).
As far as I can tell ruby is invoked, it's just unhappy with the #! in the test.rb and refuses to run the script. I cannot remove the #! because that's how the script is executed in the first place.
Any workarounds for this?
So, I cannot use rbenv/rvm/etc. There is more logic in the wrapper than this, but this is the gist of it.
Looks to me like the arguments are not being passed to Ruby in "$#". I don't think the bang-hash line is the problem.
I don't see anything in your script which actually passes the contents of test.rb to wrapper.sh, which is the bigger issue.
Perhaps the real problem can be solved by some other means? For example, is the problem you're trying to solve to run arbitrary commands prior to the invocation of any Ruby script from the command line? Perhaps it can be approached that way...
It looks like Ruby just checks that the hash-bang line contains "ruby": https://github.com/ruby/ruby/blob/v2_2_2/ruby.c#L1580 So basically having ruby somewhere in the #! line is all it takes.
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.
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.
I have a class with an instance method that runs RSpec using the %x[] notation:
class TestRunner
def run_rspec
# do stuff
%x[rspec spec -c -f documentation]
# do more stuff
end
end
When I do this:
> tr = TestRunner.new
> tr.run_rspec
The documentation (group and example names) does not appear in the console.
To contrast, when I run rspec straight from the command line I get this:
$ rspec spec -c -f documentation
a group name
an example
another example
...
I don't want to do this:
puts %x[rspec spec -c -f documentation
Because then the output all spits out in one huge clump at the very end. I want it to run in "real time," with each example showing up as each test is run.
Is there a way, with the setup I have, to get RSpec to announce what it's doing, as it's doing it (as it does when run normally from the command line)?
I've been advised that system() and the other shell methods can be dangerous to use, so I've opted to switch to the even-better approach of using RSpec itself:
RSpec::Core::Runner.run(['spec', '-c', '-f', 'documentation'])
rather than calling it via shell from my Ruby script.
Ruby offers several options for running programs from the command line. I was using %x[], the wrong choice for my use case.
Solution: Use system(), not %x[] -- rspec will write to STDOUT in real-time when I call it with system('rspec spec').
Some background in case it's helpful to anyone who stumbles upon this question:
Consider the differences between Ruby's command-line options:
%x[command] accumulates the result of command and returns it, in one chunk.
exec('command') will output command as command runs, but will replace whatever process called it -- i.e., if you use exec in your Ruby script, your Ruby script won't finish.
system('command') executes command in a subshell, and returns to the calling script.
This is why system was the choice for my script.
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