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#popen2estdoutstderr合并为一个流。
  • 要将换行符终止的字符串写入流,您可以像在简单的hello world程序中使用$stdout一样使用puts
  • 必须使用waith_thread.joinwait_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连接到终端,因为您不希望直接与用户交互。 所以我们需要别的东西。

对于标准输入,我们需要具备两种能力的东西:

  1. 父进程需要一些东西来排队子进程从stdin读取时应该获得的数据。
  2. 子进程需要提供像stdin那样的read函数的东西。

对于stdout和stderr,我们需要类似的东西:

  1. 子进程需要写入内容。 putsprint应该排队父进程应该读取的数据。
  2. 父进程需要提供read函数的东西才能获得子进程的stdout和stderr数据。

这意味着,对于stdin,stdout和stderr,我们需要三个队列(FIFO)来进行父进程和子进程之间的通信。 这些队列必须像文件一样,因为它们必须提供readwrite (用于putsprint ), closeselect (数据是否可用?)。 因此,Linux和Windows都提供匿名管道 。 这是传统(本地)进程间通信机制之一。 而且, Open3.popen3确实希望在两个不同的进程之间进行通信。 这就是Open3.popen3将stdin,stdout和stderr连接到匿名管道的原因。

每个管道,无论是匿名还是命名,都有一个有限大小的缓冲区。 这个大小取决于操作系统。 问题是:如果缓冲区已满并且进程尝试写入管道,则操作系统会暂停该进程,直到另一个进程从管道读取为止。

这可能是你的问题:

  1. 您继续向子进程提供数据,但是您没有读取子进程写入stdout的内容。
  2. 因此,我们的子进程的输出不断累积在缓冲区中,直到缓冲区已满。
  3. 这是操作系统暂停子进程( putsprint块)的时间。
  4. 现在,您仍然可以将数据提供给连接到子进程的stdin的匿名管道,直到累积了太多的stdin数据。 stdin管道的缓冲区已满。 然后操作系统将挂起父进程( stdin.write将阻塞)。

我建议你使用Open3.capture2eOpen3.popen3周围的类似包装器。 您可以使用关键字参数将数据传递到子:stdin_data

如果您坚持“以交互方式”与子流程进行通信,则需要了解IO.select或使用multithreading。 这两个都是一个很大的挑战。 更好地使用Open3.capture*