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")
I'd like to expand & clarify chaos's answer a bit.
If you surround your command with backticks, then you don't need to (explicitly) call system() at all. The backticks execute the command and return the output as a string. You can then assign the value to a variable like so:
output = `ls`
p output
or
printf output # escapes newline chars
ls #{filename}
. –
Rufinaruford `ls #{filename}`
–
Indecorous command 2>&1
–
Geochemistry %x
is for. –
Impertinent output = `ls`.tr("\n","")
–
Sc filename_sane
or filename_escd
) and only use those in dangerous contexts such as this. –
Minerva Be aware that all the solutions where you pass a string containing user provided values to system
, %x[]
etc. are unsafe! Unsafe actually means: the user may trigger code to run in the context and with all permissions of the program.
As far as I can say only system
and Open3.popen3
do provide a secure/escaping variant in Ruby 1.8. In Ruby 1.9 IO::popen
also accepts an array.
Simply pass every option and argument as an array to one of these calls.
If you need not just the exit status but also the result you probably want to use Open3.popen3
:
require 'open3'
stdin, stdout, stderr, wait_thr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets(nil)
stdout.close
stderr.gets(nil)
stderr.close
exit_code = wait_thr.value
Note that the block form will auto-close stdin, stdout and stderr- otherwise they'd have to be closed explicitly.
More information here: Forming sanitary shell commands or system calls in Ruby
gets
calls should pass the argument nil
, as otherwise we just get the first line of the output. So e.g. stdout.gets(nil)
. –
Hulky Open3.popen3
is missing a major problem: If you have a subprocess that writes more data to stdout than a pipe can hold, subprocess gets suspend in stderr.write
, and your program gets stuck in stdout.gets(nil)
. –
Tun Just for the record, if you want both (output and operation result) you can do:
output=`ls no_existing_file` ; result=$?.success?
output=`ls no_existing_file 2>&1`; result=$?.success?
–
Isochronal $?
is a global variable and thus I think it is not thread-safe –
Teddi 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')
require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
Note that the block form will auto-close stdin, stdout and stderr- otherwise they'd have to be closed explicitly. –
Ceasefire capture2
, capture2e
and capture3
also close them std*s automatically. (At the very least, I never ran into the problem on my end.) –
Klaxon Open3#popen2
, popen2e
and popen3
with a predefined block: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… –
Klaxon echo "#{untrusted}"
(with the double quotes) is not safe. Let untrusted
be "; date; echo"
. –
Tutelary You can use system() or %x[] depending what kind of result you need.
system() returning true if the command was found and ran successfully, false otherwise.
>> s = system 'uptime'
10:56 up 3 days, 23:10, 2 users, load averages: 0.17 0.17 0.14
=> true
>> s.class
=> TrueClass
>> $?.class
=> Process::Status
%x[..] on the other hand saves the results of the command as a string:
>> result = %x[uptime]
=> "13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> p result
"13:16 up 4 days, 1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> result.class
=> String
Th blog post by Jay Fields explains in detail the differences between using system, exec and %x[..] .
%x[..]
it's the compact and working solution! –
Unify If you need to escape the arguments, in Ruby 1.9 IO.popen also accepts an array:
p IO.popen(["echo", "it's escaped"]).read
In earlier versions you can use Open3.popen3:
require "open3"
Open3.popen3("echo", "it's escaped") { |i, o| p o.read }
If you also need to pass stdin, this should work in both 1.9 and 1.8:
out = IO.popen("xxd -p", "r+") { |io|
io.print "xyz"
io.close_write
io.read.chomp
}
p out # "78797a"
You use backticks:
`ls`
ruby -e '%x{ls}'
- note, no output. (fyi %x{}
is equivalent to backticks.) –
Newspaper sh
would echo the output to the console (i.e. STDOUT) as well as return it. This doesn't. –
Chilly 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 Simon Hürlimann already explained, Open3 is safer than backticks etc.
require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }
Note that the block form will auto-close stdin, stdout and stderr- otherwise they'd have to be closed explicitly.
While using backticks or popen is often what you really want, it doesn't actually answer the question asked. There may be valid reasons for capturing system
output (maybe for automated testing). A little Googling turned up an answer I thought I would post here for the benefit of others.
Since I needed this for testing my example uses a block setup to capture the standard output since the actual system
call is buried in the code being tested:
require 'tempfile'
def capture_stdout
stdout = $stdout.dup
Tempfile.open 'stdout-redirect' do |temp|
$stdout.reopen temp.path, 'w+'
yield if block_given?
$stdout.reopen stdout
temp.read
end
end
This method captures any output in the given block using a tempfile to store the actual data. Example usage:
captured_content = capture_stdout do
system 'echo foo'
end
puts captured_content
You can replace the system
call with anything that internally calls system
. You could also use a similar method to capture stderr
if you wanted.
If you want the output redirected to a file using Kernel#system
, you can do modify descriptors like this:
redirect stdout and stderr to a file(/tmp/log) in append mode:
system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])
For a long running command, this will store the output in real time. You can also, store the output using a IO.pipe and redirect it from Kernel#system.
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
capture
was removed from Rails version 5 and above, for thread safety reasons. See PR github.com/rails/rails/pull/13392 –
Wernsman If you want to keep using system
you can redirect the output to a pipe:
r, w = IO.pipe
system("ls", out: w)
w.close
output = r.read
I didn't find this one here so adding it, I had some issues getting the full output.
You can redirect STDERR to STDOUT if you want to capture STDERR using backtick.
output = `grep hosts /private/etc/* 2>&1`
source: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
Short answer for most convenient way is:
require 'open3'
stdout_str, stderr_str, status = Open3.capture3(cmd)
puts "exit status: #{status.exitstatus} stdout: #{stdout_str}"
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
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0
© 2022 - 2025 — McMap. All rights reserved.