我有一个小的ruby脚本,其中mysql
导入方式:mysql -u <user> -p<pass> -h <host> <db> < file.sql
,但使用Open3.popen3
来执行此操作。这就是我到目前为止所做的:
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
这实际上是在做这项工作,但是有一件事困扰着我,关心我希望看到的输出。
如果我将第一行更改为:
mysqlimp = "mysql -v -u #{mysqllocal['user']} " #note the -v
然后整个脚本永远挂起。
我想,这是因为读取和写入流相互阻塞,我还猜测需要定期刷新stdout
,以便继续使用stdin
。换句话说,只要stdout
的缓冲区已满,进程将等待直到它被刷新,但由于这是在最底部完成的,所以从未发生过。
我希望有人可以验证我的理论吗?我怎样才能编写打印出stdout
所有内容的代码,并将所有内容写入stdin
?
提前致谢!
答案 0 :(得分:12)
Open3#popen2e
将stdout
和stderr
合并为一个流。puts
一样在简单的hello world程序中使用$stdout
。waith_thread.join
或wait_thread.value
等待子进程终止。示例:
require 'open3'
cmd = 'sh'
Open3.popen2e(cmd) do |stdin, stdout_stderr, wait_thread|
Thread.new do
stdout_stderr.each {|l| puts l }
end
stdin.puts 'ls'
stdin.close
wait_thread.value
end
您的代码已修复:
require 'open3'
mysqldump = # ...
mysqlimp = "mysql -u #{mysqllocal['user']} "
mysqlimp << "-h #{mysqllocal['host']} "
mysqlimp << "-p#{mysqllocal['pass']} "
mysqlimp << "#{mysqllocal['db']}"
Open3.popen2e(mysqlimp) do |stdin, stdout_stderr, wait_thread|
Thread.new do
stdout_stderr.each {|l| puts l }
end
stdin.puts "DROP DATABASE IF EXISTS #{mysqllocal['db']};"
stdin.puts "CREATE DATABASE #{mysqllocal['db']};"
stdin.puts "USE #{mysqllocal['db']};"
stdin.close
wait_thread.value
end
答案 1 :(得分:4)
每当从命令行或通过fork
启动进程时,进程都会从父进程继承stdin,stdout和stderr。这意味着,如果您的命令行在终端中运行,则新进程的stdin,stdout和stderr将连接到终端。
Open3.popen3
没有将stdin,stdout和stderr连接到终端,因为您不希望直接用户交互。所以我们还需要别的东西。
对于stdin,我们需要具备两种能力的东西:
read
函数的东西。对于stdout和stderr,我们需要类似的东西:
puts
和print
应该排队父进程应该读取的数据。read
函数的东西才能获取子进程的stdout和stderr数据。这意味着,对于stdin,stdout和stderr,我们需要三个队列(FIFO)来进行父进程和子进程之间的通信。这些队列必须像文件一样,因为他们必须提供read
,write
(puts
和print
),close
和{{1} (数据是否可用?)。
因此,Linux和Windows都提供anonymous pipes。这是传统(本地)进程间通信机制之一。而且,select
确实希望在两个不同的进程之间进行通信。这就是Open3.popen3
将stdin,stdout和stderr连接到匿名管道的原因。
每个管道,无论是匿名还是命名,都有一个有限大小的缓冲区。这个大小取决于操作系统。问题是:如果缓冲区已满并且进程尝试写入管道,则操作系统会暂停该进程,直到另一个进程从管道中读取。
这可能是你的问题:
Open3.popen3
或puts
块)的时候。print
将阻止)。我建议您使用stdin.write
或Open3.capture2e
周围的类似包装器。您可以使用关键字参数Open3.popen3
将数据传递到子流程。
如果您坚持以交互方式与子流程进行通信,则需要了解:stdin_data
或使用多线程。这两个都是一个很大的挑战。更好地使用IO.select
。