Ruby-Open3.popen3 /如何打印输出
我有一个小的ruby脚本,它执行mysql
导入: mysql -u -p -h < 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
的缓冲区已满,进程将等待直到刷新,但由于这是在最底部完成的,所以从未发生过。
我希望有人可以validation我的理论吗? 我怎么能编写打印出stdout
所有内容的代码,并将所有内容写入stdin
?
谢谢你!
- 由于您只是写入stdout,因此您可以简单地使用
Open3#popen2e
将stdout
和stderr
合并为一个流。 - 要将换行符终止的字符串写入流,您可以像在简单的hello world程序中使用
$stdout
一样使用puts
。 - 必须使用
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
无论何时从命令行或通过fork
启动进程,进程都会从父进程inheritancestdin,stdout和stderr。 这意味着,如果您的命令行在终端中运行,则新进程的stdin,stdout和stderr将连接到终端。
另一方面, Open3.popen3
不会将stdin,stdout和stderr连接到终端,因为您不希望直接与用户交互。 所以我们需要别的东西。
对于标准输入,我们需要具备两种能力的东西:
- 父进程需要一些东西来排队子进程从stdin读取时应该获得的数据。
- 子进程需要提供像stdin那样的
read
函数的东西。
对于stdout和stderr,我们需要类似的东西:
- 子进程需要写入内容。
puts
和print
应该排队父进程应该读取的数据。 - 父进程需要提供
read
函数的东西才能获得子进程的stdout和stderr数据。
这意味着,对于stdin,stdout和stderr,我们需要三个队列(FIFO)来进行父进程和子进程之间的通信。 这些队列必须像文件一样,因为它们必须提供read
, write
(用于puts
和print
), close
和select
(数据是否可用?)。 因此,Linux和Windows都提供匿名管道 。 这是传统(本地)进程间通信机制之一。 而且, Open3.popen3
确实希望在两个不同的进程之间进行通信。 这就是Open3.popen3
将stdin,stdout和stderr连接到匿名管道的原因。
每个管道,无论是匿名还是命名,都有一个有限大小的缓冲区。 这个大小取决于操作系统。 问题是:如果缓冲区已满并且进程尝试写入管道,则操作系统会暂停该进程,直到另一个进程从管道读取为止。
这可能是你的问题:
- 您继续向子进程提供数据,但是您没有读取子进程写入stdout的内容。
- 因此,我们的子进程的输出不断累积在缓冲区中,直到缓冲区已满。
- 这是操作系统暂停子进程(
puts
或print
块)的时间。 - 现在,您仍然可以将数据提供给连接到子进程的stdin的匿名管道,直到累积了太多的stdin数据。 stdin管道的缓冲区已满。 然后操作系统将挂起父进程(
stdin.write
将阻塞)。
我建议你使用Open3.capture2e
或Open3.popen3
周围的类似包装器。 您可以使用关键字参数将数据传递到子:stdin_data
。
如果您坚持“以交互方式”与子流程进行通信,则需要了解IO.select
或使用multithreading。 这两个都是一个很大的挑战。 更好地使用Open3.capture*
。