When to Use Each Method of Launching a Subprocess in Ruby

When to use each method of launching a subprocess in Ruby

  1. use backticks when you want to easily capture the output of a program in a variable. you probably only want to use this for short-running programs, because this will block.

  2. system is convenient in two different cases:

    a. You have a long running program and you want the output to print as it runs (e.g. system("tar zxvf some_big_tarball.tar.gz"))

    b. system can bypass the shell expansion like exec (compare the output of system "echo *" and system "echo", "*")

    system blocks until the subprocess has exited.

  3. fork has a couple different use cases as well:

    a. You want to run some ruby code in a separate process (e.g. fork { .... }

    b. You want to run a child process (or different program) without blocking progress of your script fork { exec "bash" }.

    fork is your friend if you want to daemonize your program.

  4. IO.popen is useful when you need to interact with the standard out and standard in of a program. Note that it doesn't capture standard err, so you need to redirect that with 2>&1 if you care about that.

  5. popen3 gives you a separate file descriptor for standard error (for when you need to capture that separately from standard out)

  6. PTY.spawn is necessary when you want the spawned program to behave like you are running from the terminal. See the difference of grep --color=auto pat file when spawned with system vs PTY.spawn

Communicate with subprocess in Ruby

The code you wrote works for me with the cat program:

IO.popen("cat", "r+") do |io|
io.puts("abcdef\n")
answer1 = io.gets
puts answer1
io.puts("#{answer1.chomp}ghijkl\n")
answer2 = io.gets
puts answer2
end

This prints

abcdef
abcdefghijkl

Maybe you need to flush your io after puts?

Equivalent of subprocess.Popen() in Ruby?

Open3#popen3 will be useful.

Assuming you have a test.rb file as shown below:

v = gets.chomp
puts "#{v} @ #{Time.new}"

You could do:

require "open3"

stdin, stdout, stderr, wait_thr = Open3.popen3("ruby test.rb")

stdin.puts("hi")
puts stdout.gets(nil)
#=> hi @ 2016-02-05 19:18:52 +0530

stdin.close
stdout.close
stderr.close

For multiple sub-processes to execute in parallel, you can use threads as shown below:

require "open3"

t1 = Thread.new do |t|
stdin, stdout, stderr, wait_thr1 = Open3.popen3("ruby test.rb")
stdin.puts("Hi")
puts stdout.gets(nil)
end

t2 = Thread.new do |t|
stdin, stdout, stderr, wait_thr1 = Open3.popen3("ruby test.rb")
stdin.puts("Hello")
puts stdout.gets(nil)
end

t1.join
t2.join

Handle subprocesses in ruby

If I change it to

#!/usr/bin/env ruby

def start
@rtorrentIO ||= IO.popen("rtorrent",'w') #default mode is 'r'
sleep 0.5
end
def stop
Process.kill(9, @rtorrentIO.pid) if defined? @rtorrentIO
@rtorrentIO = nil
end

start


sleep 0.5
#some code there
stop

to make it into a self-contained example, it works fine for me. You should probably open the pipe for writing (..,"w"), since you're uninterested in the output (and if you're not interested in writing into it, then there's no point in opening the process with popen and you're better off using things like pid = Process.spawn('rtorrent')).

UPDATE
The problem you're experiencing is due to rtorrent messing up your terminal by writing special terminal control sequences (intentionally or not) to its output. This'll happen from time to time (catting a random binary file will very likely mess up your terminal as you'll likely encounter a byte sequence that's somehow special to the terminal). The solution is to run reset in the terminal (see man reset for more details)--you'll have to type it in blindly.

If you don't want rtorrent to do that, redirecting its stdout and stderr (these are normally bound to your terminal) will most likely stop it from writing to your terminal. Apart from that, you could do system reset in the stop method to fix what rtorrent has done.

Below is a combination of the above:

#!/usr/bin/env ruby

def start
@pid ||= Process.spawn "rtorrent" #, err: '/dev/null', out: '/dev/null'
sleep 0.5
end
def stop
Process.kill(9, @pid) if @pid
@pid = nil
system 'reset'
end

start


sleep 0.5
#some code there
stop

Ruby open interactive sub process within the shell

What is wrong with system for this case?

  • The exec ruby command replaces the running process, so it will not return to your code.
  • The Open3 library is used when you want to capture stdout and stderr.

Isn't this what you are looking for?

puts "here"
system "cfdisk"
puts 'there'

If you have some screen related issues, this is another issue that you might be able to resolve with different TERM value in the environment variable.

run a subprocess from ruby without waiting for it to return

in Ruby 1.9.3, u can use

 Process.fork do
# do your long time job
end

How to control a subprocess stdin, stdout, etc within Ruby?

Something like this, perhaps?

IO.popen 'sh', 'r+' do |io|
io.puts 'echo how now brown cow | tr a-z A-Z'
result = io.gets
p [:result, result.size, result]
end

How to wait for process to finish using IO.popen?

I think you'd need to assign the results from the IO.popen calls within the cycle to the variables, and keep calling read() on them until eof() becomes true on all.

Then you know that all the programs have finished their execution and you can start another_cmd.



Related Topics



Leave a reply



Submit