description |
---|
Ruby System Shell Command Execution |
Some things to think about when choosing between these ways are:
- Are you going to interact with none interactive shell, like
ncat
? 2. Do you just want stdout or do you need stderr as well? Or even separated out? - How big is your output? Do you want to hold the entire result in memory?
- Do you want to read some of your output while the subprocess is still running?
- Do you need result codes?
- Do you need a ruby object that represents the process and lets you kill it on demand?
The following ways are applicable on all operating systems.
>> exec('date')
Sun Sep 27 00:39:22 AST 2015
RubyFu( ~ )->
>> system 'date'
Sun Sep 27 00:38:01 AST 2015
#=> true
Have you ever wondered about how to do deal with interactive commands like passwd
or ncat
sessions in Ruby? If you're familiar with Python, you've probably used python -c 'import pty; pty.spawn("/bin/sh")'
. In Ruby it's really easy using exec
or system
. The main trick is to forward STDERR to STDOUT so you can see system errors.
exec
ruby -e 'exec("/bin/sh 2>&1")'
system
ruby -e 'system("/bin/sh 2>&1")'
>> `date`
#=> "Sun Sep 27 00:38:54 AST 2015\n"
>> IO.popen("date") { |f| puts f.gets }
Sun Sep 27 00:40:06 AST 2015
#=> nil
require 'open3'
stdin, stdout, stderr = Open3.popen3('dc')
#=> [#<IO:fd 14>, #<IO:fd 16>, #<IO:fd 18>, #<Process::Waiter:0x00000002f68bd0 sleep>]
>> stdin.puts(5)
#=> nil
>> stdin.puts(10)
#=> nil
>> stdin.puts("+")
#=> nil
>> stdin.puts("p")
#=> nil
>> stdout.gets
#=> "15\n"
Kernel.spawn executes the given command in a subshell. It returns immediately with the process id.
pid = Process.spawn("date")
Sun Sep 27 00:50:44 AST 2015
#=> 12242
>> %x"date"
#=> Sun Sep 27 00:57:20 AST 2015\n"
>> %x[date]
#=> "Sun Sep 27 00:58:00 AST 2015\n"
>> %x{date}
#=> "Sun Sep 27 00:58:06 AST 2015\n"
>> %x$'date'$
#=> "Sun Sep 27 00:58:12 AST 2015\n"
require 'rake'
>> sh 'date'
date
Sun Sep 27 00:59:05 AST 2015
#=> true
You can also inject commands into other terminals using Ruby and IOCTL syscall
#!/usr/bin/env ruby
# Target TTY device path (Example: /dev/pts/0)
tty = ARGV[0]
cmd = ARGV[1] + "\n"
abort("Usage: #{__FILE__} <device> <command>") unless ARGV[0] && ARGV[1]
abort("#{__FILE__}: Must be run as root") unless Process.uid == 0
dev = IO.new(IO.sysopen(tty))
abort("The given device is not a tty") unless dev.tty?
cmd.each_char do |c|
dev.ioctl(0x5412, c)
end
To check the status of the backtick operation you can execute $?.success?
>> `date`
=> "Sun Sep 27 01:06:42 AST 2015\n"
>> $?.success?
=> true
A great flow chart has been made on stackoverflow