If I call a command using Kernel#system in Ruby, how do I get its output?
system("ls")
If I call a command using Kernel#system in Ruby, how do I get its output?
system("ls")
The straightforward way to do this correctly and securely is to use Open3.capture2(), Open3.capture2e(), or Open3.capture3().
Using ruby's backticks and its %x alias are NOT SECURE UNDER ANY CIRCUMSTANCES if used with untrusted data. It is DANGEROUS, plain and simple:
untrusted = "; date; echo"
out = `echo #{untrusted}` # BAD
untrusted = '"; date; echo"'
out = `echo "#{untrusted}"` # BAD
untrusted = "'; date; echo'"
out = `echo '#{untrusted}'` # BAD
The system function, in contrast, escapes arguments properly if used correctly:
ret = system "echo #{untrusted}" # BAD
ret = system 'echo', untrusted # good
Trouble is, it returns the exit code instead of the output, and capturing the latter is convoluted and messy.
The best answer in this thread so far mentions Open3, but not the functions that are best suited for the task. Open3.capture2, capture2e and capture3 work like system, but returns two or three arguments:
out, err, st = Open3.capture3("echo #{untrusted}") # BAD
out, err, st = Open3.capture3('echo', untrusted) # good
out_err, st = Open3.capture2e('echo', untrusted) # good
out, st = Open3.capture2('echo', untrusted) # good
p st.exitstatus
Another mentions IO.popen(). The syntax can be clumsy in the sense that it wants an array as input, but it works too:
out = IO.popen(['echo', untrusted]).read # good
For convenience, you can wrap Open3.capture3() in a function, e.g.:
#
# Returns stdout on success, false on failure, nil on error
#
def syscall(*cmd)
begin
stdout, stderr, status = Open3.capture3(*cmd)
status.success? && stdout.slice!(0..-(1 + $/.size)) # strip trailing eol
rescue
end
end
Example:
p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')
Yields the following:
nil
nil
false
false
/usr/bin/which <— stdout from system('which', 'which')
true <- p system('which', 'which')
"/usr/bin/which" <- p syscall('which', 'which')
Another way is:
f = open("|ls")
foo = f.read()
Note that's the "pipe" character before "ls" in open. This can also be used to feed data into the programs standard input as well as reading its standard output.
I found that the following is useful if you need the return value:
result = %x[ls]
puts result
I specifically wanted to list the pids of all the Java processes on my machine, and used this:
ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
As a direct system(...) replacement you may use Open3.popen3(...)
Further discussion: http://tech.natemurray.com/2007/03/ruby-shell-commands.html
Simplest solution to capture standard output into a variable named val:
val = capture(:stdout) do
system("pwd")
end
puts val
shortened version:
val = capture(:stdout) { system("ls") }
capture method is provided by active_support/core_ext/kernel/reporting.rb
Simlarly we can also capture standard errors too with :stderr
You can use a gem, called Frontkick
Frontkick.exec("echo *")
And here is how to check and read it:
result = Frontkick.exec("echo *")
puts result.successful? #=> true if exit_code is 0
puts result.success? #=> alias to successful?, for compatibility with Process::Status
puts result.stdout #=> stdout output of the command
puts result.stderr #=> stderr output of the command
puts result.exit_code #=> exit_code of the command
puts result.status #=> alias to exit_code
puts result.exitstatus #=> alias to exit_code, for compatibility with Process::Status
puts result.duration #=> the time used to execute the command
Github https://github.com/sonots/frontkick#frontkick
Gem Page https://rubygems.org/gems/frontkick