When to use each method of launching a subprocess in Ruby
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.
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 likeexec
(compare the output ofsystem "echo *"
andsystem "echo", "*"
)system blocks until the subprocess has exited.
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.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 with2>&1
if you care about that.popen3
gives you a separate file descriptor for standard error (for when you need to capture that separately from standard out)PTY.spawn
is necessary when you want the spawned program to behave like you are running from the terminal. See the difference ofgrep --color=auto pat file
when spawned withsystem
vsPTY.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 (cat
ting 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
Why Does Adding "Sleep 1" in an After Hook Cause This Rspec/Capybara Test to Pass
Nokogiri Gem Installation Error
Running a Command from Ruby Displaying and Capturing the Output
How to Edit or Write on Existing Pdf With Ruby
Flutter Pod Install Problem - Undefined Method 'Each_Child' For #≪Dir:0X00007Fa6F7E2Ec80≫
Popen Getting Pid of Newly Run Process
Character Encoding Issue in Rails V3/Ruby 1.9.2
Looping in a Spiral Outside-In
Inheriting Class Methods from Modules/Mixins in Ruby
Dynamic Class Definition with a Class Name
How to Use "Rvm --Default" on Macosx
Convert Unicode Codepoint to String Character in Ruby
How to Avoid Trailing Empty Items Being Removed When Splitting Strings
Rails Assets:Precompile Yarn Executable Was Not Detected in the System
Installing Rvm (Ruby Version Manager)
Test If Variable Matches Any of Several Strings W/O Long If-Elsif Chain, or Case-When