353

If I call a command using Kernel#system in Ruby, how do I get its output?

system("ls")
Per Lundberg
  • 3,837
  • 1
  • 36
  • 46
Macha
  • 14,366
  • 14
  • 57
  • 69
  • 1
    You may want to have a look at [this thread in comp.lang.ruby](http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/a274d5d47feae95?pli=1) – Manrico Corazzi Mar 27 '09 at 15:19
  • This is a very hand thread, thanks. The class for running commands and getting feedback is great in the sample code. – ylluminate Nov 01 '11 at 15:16
  • 5
    For future googlers. If you want to learn about other system command calls and their differences, [see this SO answer](http://stackoverflow.com/a/18623297/52317). – Uzbekjon Apr 22 '16 at 08:12

19 Answers19

387

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
Community
  • 1
  • 1
Craig Walker
  • 49,871
  • 54
  • 152
  • 212
  • 4
    what if I need to give a variable as part of my command? That is, what would something like system("ls " + filename) translate into when backticks are to be used? – Vijay Dev Dec 27 '09 at 17:08
  • 47
    You can do expression evaluation just as you would with regular strings: `ls #{filename}`. – Craig Walker Dec 27 '09 at 17:38
  • 1
    Hrm, makdown ate my formatting. There are back ticks ahead of the "l" in "ls" and after the "}" in "#{filename}". – Craig Walker Dec 27 '09 at 17:40
  • Craig - in markdown you can quote with the backslash character: `\`ls #{filename}\`` – danio Nov 02 '10 at 14:05
  • 1
    Does this capture all output? – knowncitizen Jan 21 '11 at 17:28
  • No, it only captures stdout, not stderr. – Craig Walker Jan 21 '11 at 18:06
  • 43
    This answer isn't advisable: it introduces the new problem of unsanitized user input. – Dogweather Apr 02 '12 at 10:46
  • 5
    @Dogweather: that may be true, but is it any different than any of the other methods? – Craig Walker Apr 02 '12 at 15:01
  • 21
    if you want to capure stderr just put 2>&1 at the end of your command. e.g output = `command 2>&1` – micred May 16 '12 at 09:56
  • I think the answer @martin-gross provides is the cleaner solution; while both work, this is what `%x` is for. – Tom Harrison Oct 05 '12 at 14:30
  • backticks is not equivalent to system, you can't pass environment, you can separate stdout and stderrr etc – Anurag Uniyal Mar 21 '13 at 22:30
  • @CraigWalker: I think micred's comment should be added to your answer. It's rather helpful. if you want to capure stderr just put 2>&1 at the end of your command. e.g output = command 2>&1 – Nickolay Kondratenko Oct 16 '13 at 06:19
  • 1
    It's often necessary to cut a trailing newline with `output = \`ls\`.tr("\n","")` – orkoden Oct 13 '14 at 10:15
  • 2
    @Dogweather It only introduces unsanitized user input if variables with user input that hasn't yet been sanitized are used.  An experienced Ruby programmer would be careful to label relevant vars (e.g. `filename_sane` or `filename_escd`) and only use those in dangerous contexts such as this. – Slipp D. Thompson Apr 11 '15 at 20:50
  • Does anyone know whether the backtick operator is actually thread-safe? That is, if I pass different commands from different threads to it, and store the output in thread-local variables, are the variables guaranteed to only contain the output from the command I called in a particular thread, or might the output be manged with the output form command I called in another thread? – sschuberth Jan 22 '16 at 14:14
  • @CraigWalker. Your printf is not syntactically correct. – Frak Apr 26 '18 at 14:00
  • Backticks are perfectly fine in a program that does not accept user input, such as the one shown here. The only way to modify the system call is to modify the source, and if someone can modify the source, well... – WillHaslett Jul 19 '21 at 19:47
  • Be aware that using system and backticks, are similar, but not the same. When in doubt, try both. – Rich_F Aug 27 '21 at 17:20
  • This works only if you command is static. If you try to build dynamic command like "ls #{directory_path}" it wont work. – Bob May 09 '22 at 10:42
262

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

Community
  • 1
  • 1
Simon Hürlimann
  • 4,181
  • 3
  • 20
  • 23
  • 27
    This is the only answer that actually answers the question and solves the problem without introducing new ones (unsanitized input). – Dogweather Apr 02 '12 at 10:42
  • 2
    Thanks! This is the sort of answer I was hoping for. One correction: the `gets` calls should pass the argument `nil`, as otherwise we just get the first line of the output. So e.g. `stdout.gets(nil)`. – Greg Price Jan 04 '13 at 07:10
  • 3
    stdin, stdout and stderr should be [closed explicitly in non-block form](http://www.ruby-doc.org/stdlib-2.0/libdoc/open3/rdoc/Open3.html#method-c-popen3). – Yarin Jan 12 '14 at 05:22
  • Does anybody know if something changed in Ruby 2.0 or 2.1? Edits or comments would be appreciated;-) – Simon Hürlimann Jan 18 '14 at 22:12
  • 1
    I think that the discussion around `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)`. – hagello Oct 31 '14 at 11:50
  • @simon I am using the same code but I dont know for some reason in nginx+passenger the application is not waiting for the response from the script I dont know what is happening, please check the issue here http://stackoverflow.com/questions/28696685/rails-controller-returning-nil-response-while-running-casperjs-test-cases-with-s – Saurabh Udaniya Feb 25 '15 at 10:05
  • block form here (near bottom): https://makandracards.com/makandra/44452-running-external-commands-with-open3. Note that the string marked '/some/command' should include all the arguments. I was trying stdin.puts "my arguments", but that's only needed if the executed command consumes user input – kraftydevil Jun 25 '19 at 21:08
180

Just for the record, if you want both (output and operation result) you can do:

output=`ls no_existing_file` ;  result=$?.success?
jdl
  • 17,702
  • 4
  • 51
  • 54
FernandoFabreti
  • 1,883
  • 1
  • 12
  • 2
  • 13
    That only captures stdout, and stderr goes to the console. To get stderr, use: `output=\`ls no_existing_file 2>&1\`; result=$?.success?` – peterept Jul 09 '12 at 01:44
  • 10
    This answer is **unsafe** and shouldn't be used -- if the command is anything but a constant, then the backtick syntax is likely to cause a bug, possibly a security vulnerability. (And even if it is a constant, it will probably cause someone to use it for a non-constant later and cause a bug.) See [Simon Hürlimann's answer](http://stackoverflow.com/a/5970819/378130) for a correct solution. – Greg Price Jan 03 '13 at 03:15
  • 29
    kudos to Greg Price for understanding about the need to escape user input, but it is not correct to say this answer as written is unsafe. The Open3 method mentioned is more complicated and introduces more dependencies, and the argument that someone will "use it for a non-constant later" is a strawman. True, you probably wouldn't use them in a Rails app, but for a simple system utility script with no possibility of untrusted user input, backticks are perfectly fine and nobody should be made to feel bad about using them. – sbeam Jun 03 '14 at 12:45
  • Two major issues with this solution are 1. **user input is not escaped** 2. `$?` is a global variable and thus I think it is **not thread-safe** – collimarco Oct 31 '21 at 19:10
90

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')
Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154
  • 2
    This is the correct answer. It is also the most informative. The only thing missing is a warning about closing the std*s. See [this other comment](http://stackoverflow.com/a/21071595/213191): ```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](http://www.ruby-doc.org/stdlib-2.0/libdoc/open3/rdoc/Open3.html#method-c-popen3). – Peter H. Boling Feb 04 '14 at 17:02
  • @PeterH.Boling: Best I'm aware, the `capture2`, `capture2e` and `capture3` also close them std*s automatically. (At the very least, I never ran into the problem on my end.) – Denis de Bernardy Feb 04 '14 at 17:14
  • without using the block form there is no way for a codebase to know when something should be closed, so I *highly* doubt they are being closed. You probably never ran into a problem because not closing them won't cause problems in a short lived process, and if you restart a long-running process often enough, otto won't show up there either unless you are opening std*s in a loop. Linux has a high file descriptor limit, which you can hit, but until you hit it you won't see the "bug". – Peter H. Boling Mar 09 '14 at 20:20
  • 2
    @PeterH.Boling: No no, see the source code. The functions are just wrappers around `Open3#popen2`, `popen2e` and `popen3` with a predefined block: http://www.ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/Open3.html#method-c-capture2 – Denis de Bernardy Mar 10 '14 at 08:28
  • 1
    @Dennis de Barnardy Perhaps you missed that I linked to the same class documentation (albeit for Ruby 2.0.0, and a different method. http://ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/Open3.html#method-c-popen3 From the example: ``` stdin, stdout, stderr, wait_thr = Open3.popen3([env,] cmd... [, opts]) pid = wait_thr[:pid] # pid of the started process ... stdin.close # stdin, stdout and stderr should be closed explicitly in this form. stdout.close stderr.close ``` I was just quoting the documentation. "# stdin, stdout and stderr should be closed explicitly in this form." – Peter H. Boling Nov 21 '15 at 01:44
  • `echo "#{untrusted}"` (with the double quotes) is **not** safe. Let `untrusted` be `"; date; echo"`. – Andreas Krey Apr 06 '16 at 05:39
  • Good catch @AndreasKrey! – Denis de Bernardy Apr 06 '16 at 13:50
61

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[..] .

Martin Gross
  • 929
  • 7
  • 5
  • 2
    Thanks for the tip of using %x[]. It just solved a problem I had where I used back ticks in a ruby script in Mac OS X. When running the same script on a Windows machine with Cygwin, it failed because of the back ticks, but worked with %x[]. – Henrik Warne Nov 02 '12 at 17:13
  • Thanks a lot! `%x[..]` it's the compact and working solution! – SwiftyFinch Dec 12 '20 at 16:18
28

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"
Lri
  • 26,768
  • 8
  • 84
  • 82
20

You use backticks:

`ls`
chaos
  • 122,029
  • 33
  • 303
  • 309
20

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.

dwc
  • 24,196
  • 7
  • 44
  • 55
  • Just used this to read standard output from an aws cli command in order to read the json and not the official return value of 'true' – kraftydevil Aug 29 '17 at 20:45
16

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.

Community
  • 1
  • 1
Yarin
  • 173,523
  • 149
  • 402
  • 512
16

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]
15

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.

Matilda Smeds
  • 1,384
  • 11
  • 18
Eric Anderson
  • 3,692
  • 4
  • 31
  • 34
10

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.

Ashrith
  • 6,745
  • 2
  • 29
  • 36
7

As a direct system(...) replacement you may use Open3.popen3(...)

Further discussion: http://tech.natemurray.com/2007/03/ruby-shell-commands.html

3

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

  • 1
    `capture` was removed from Rails version 5 and above, for thread safety reasons. See PR https://github.com/rails/rails/pull/13392 – Eliot Sykes Jul 27 '22 at 07:28
1

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

dac2009
  • 3,521
  • 1
  • 22
  • 22
1

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}"
notapatch
  • 6,569
  • 6
  • 41
  • 45
Daniel Garmoshka
  • 5,849
  • 39
  • 40
0

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

0

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
Michaël Witrant
  • 7,525
  • 40
  • 44
-1
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0
Ron Hinchley
  • 299
  • 3
  • 5