I have a little ruby script which does a mysql
import in the way: mysql -u <user> -p<pass> -h <host> <db> < file.sql
, but utilizes Open3.popen3
to do so. That is what I have so far:
mysqlimp = "mysql -u #{mysqllocal['user']} "
mysqlimp << "-h #{mysqllocal['host']} "
mysqlimp << "-p#{mysqllocal['pass']} "
mysqlimp << "#{mysqllocal['db']}"
Open3.popen3(mysqlimp) do |stdin, stdout, stderr, wthr|
stdin.write "DROP DATABASE IF EXISTS #{mysqllocal['db']};\n"
stdin.write "CREATE DATABASE #{mysqllocal['db']};\n"
stdin.write "USE #{mysqllocal['db']};\n"
stdin.write mysqldump #a string containing the database data
stdin.close
stdout.each_line { |line| puts line }
stdout.close
stderr.each_line { |line| puts line }
stderr.close
end
That is actually doing the Job, but there is one thing that bothers me, concerned to the output I would like to see.
If I change the first line to:
mysqlimp = "mysql -v -u #{mysqllocal['user']} " #note the -v
then the whole script hangs forever.
I guess, that happens because the read- and write-stream block each other and I also guess that the stdout
needs to be flushed regularly so that stdin
will go on to be consumed. In other words, as long as the buffer of the stdout
is full, the process will wait until its flushed, but since this is done at the very bottom first, that never happens.
I hope someone can verify my theory? How could I write code that does prints out everything from the stdout
and writes everything to the stdin
as well?
Thanks in ahead!
Whenever you start a process from the command line or via
fork
, the process inherits stdin, stdout and stderr from the father process. This means, if your command line runs in a terminal, stdin, stdout and stderr of the new process are connected to the terminal.Open3.popen3
, on the other hand, does not connect stdin, stdout and stderr to the terminal, because you do not want direct user interaction. So we need something else.For stdin, we need something with two abilities:
read
function like stdin does.For stdout and stderr, we need something similar:
puts
andprint
should enqueue the data, that the father process is supposed to read.read
function in order to get the stdout and stderr data of the subprocess.This means, for stdin, stdout and stderr, we need three queues (FIFO) for communication between father process and subprocess. These queues have to act a little bit like files as they have to provide
read
,write
(forputs
andprint
),close
andselect
(is data available?). Therefore, both Linux and Windows provide anonymous pipes. This is one of the conventional (local) interprocess communication mechanisms. And, well,Open3.popen3
really wants to do communication between two different processes. This is whyOpen3.popen3
connects stdin, stdout and stderr to anonymous pipes.Each pipe, be it anonymous or named, does have a buffer of limited size. This size depends on operation system. The catch is: If the buffer is full and a processes tries to write to the pipe, the operating system suspends the process until another processes reads from the pipe.
This may be your problem:
puts
orprint
blocks).stdin.write
will block).I advise you to use
Open3.capture2e
or a similar wrapper aroundOpen3.popen3
. You can pass data to the subprocess with the keyword argument:stdin_data
.If you insist on communicating with your subprocess "interactively", you need to learn about
IO.select
or using multi-threading. Both of them are quite a challenge. Better useOpen3.capture*
.Open3#popen2e
which consolidatesstdout
andstderr
into a single stream.puts
as you would with$stdout
in a simple hello world program.waith_thread.join
orwait_thread.value
to wait until the child process terminates.Example:
Your code, fixed: