Ruby allows many different ways to execute commands or sub-processes. Here I will list few of them
1) Backtick
2) system
3) exec
4) sh
5) popen3
Backtick ( `cmd` ) :
backtick returns the output of the cmd in a subshell.
output = `ls` puts "output is #{output}"
Result of the above code is
$ ruby test.rb output is amit.txt test.rb
Here the backtick operation forks the master process and the operation is executed in a new process. However this is a blocking operation. The main application waits until the result of backtick operation completes.
If there is an exception in the sub-process then that exception is given to the main process and the main process might terminate if exception is not handled.
In the following case I am executing
xxxxx
which is not a valid executable name.
outut = `xxxxxxx`
puts "output is #{output}"
Result of above code is :
$ ruby test.rb
test.rb:1:in ``': No such file or directory - xxxxxxx (Errno::ENOENT) from test.rb:1:in `<main>'
Notice that
output = `ls`
puts "output is #{output}"
puts $?.success?puts
was never executed because the backtick operation raised exception. To check the status of the backtick operation you can execute $?.success?
Notice that the last line of the result contains
true
because the backtick operation was a success.$ ruby main.rb output is lab.rb main.rb true
system :
system behaves a bit like backtick operation. However there are some differences.
First let’s look at similarities.
Just like
backtick
, system
is a blocking operation. You can get the result of the operation using $?.success?
. system
operations are also executed in a subshell.
Now the differneces between
backtick
and system
.system
eats up all the exceptions. So the main operation never needs to worry about capturing an exception raised from the child process.
output = system('xxxxxxx')
puts "output is #{output}"
Result of the above operation is given below. Notice that even when exception is raised the main program completes and the output is printed. The value of output is nil because the child process raised an exception.
$ ruby test.rb
output is
Another difference is that
system
returns true if the command was successfully performed ( exit status zero ) . It returns false for non zero exit status. Returns nil if command execution fails.
exec :
exec replaces the current process by running the external command. Let’s see an example.
Here I am in irb and I am going to execute
exec('ls')
.
$ irb
e1.9.3-p194 :001 > exec('ls')
amit.rb test.rb
amitk ~/dev/lab 1.9.3
$
I see the result but since the irb process was replaced by the
exec
process I am no longer in irb
.
Behind the scene both
system
and backtick
operations use fork
to fork the current process and then they execute the given operation using exec
.
Since
exec
replaces the current process it does not return anything if the operation is a success. If the operation fails then `SystemCallError is raised.
sh :
sh actually calls
system
under the hood. However it is worth a mention here. This method is added by FileUtils
in rake
. It allows an easy way to check the exit status of the command.
require 'rake'
sh %w(xxxxx) do |ok, res|
if !ok
abort 'the operation failed'
end
end
popen3 :
If you are going to capture
stdout
and stderr
then you should use popen3 since this method allows you to interact with stdin
, stdout
and stderr
.
I want to execute
git push heroku master
programmatically and I want to capture the output. Here is my code.
require 'open3'
cmd = 'git push heroku master'
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
puts "stdout is:" + stdout.read
puts "stderr is:" + stderr.read
end
And here is the output. It has been truncated since rest of output is not relevant to this discussion.
stdout is:
stderr is:
Heroku receiving push
Ruby/Rails app detected
Installing dependencies using Bundler version 1.2.1
Lets see an example of capturing streaming output.
require 'open3'
cmd = 'ping www.google.com'
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
while line = stdout.gets
puts line
end
end
In the above case you will get the output of ping on your terminal as if you had typed
ping www.google.com
on your terminal .
Now let’s see how to check if command succeeded or not.
require 'open3'
cmd = 'ping www.google.com'
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
exit_status = wait_thr.value
unless exit_status.success?
abort "FAILED !!! #{cmd}"
end
end
Hope this post helps in understanding the basics of executing commands from our program.
Happy Coding... :)
No comments:
Post a Comment