Ruby-Open3.popen3 /如何打印输出

时间:2014-08-15 11:25:52

标签: ruby popen3

我有一个小的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

提前致谢!

2 个答案:

答案 0 :(得分:12)

  • 由于您只是写入stdout,因此您只需使用Open3#popen2estdoutstderr合并为一个流。
  • 要将换行符终止的字符串写入流,您可以像使用puts一样在简单的hello world程序中使用$stdout
  • 您必须使用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

答案 1 :(得分:4)

每当从命令行或通过fork启动进程时,进程都会从父进程继承stdin,stdout和stderr。这意味着,如果您的命令行在终端中运行,则新进程的stdin,stdout和stderr将连接到终端。

另一方面,

Open3.popen3没有将stdin,stdout和stderr连接到终端,因为您不希望直接用户交互。所以我们还需要别的东西。

对于stdin,我们需要具备两种能力的东西:

  1. 父进程需要一些东西来排队子进程从stdin读取时应该获得的数据。
  2. 子进程需要提供像{stin一样的read函数的东西。
  3. 对于stdout和stderr,我们需要类似的东西:

    1. 子流程需要写入内容。 putsprint应该排队父进程应该读取的数据。
    2. 父进程需要提供read函数的东西才能获取子进程的stdout和stderr数据。
    3. 这意味着,对于stdin,stdout和stderr,我们需要三个队列(FIFO)来进行父进程和子进程之间的通信。这些队列必须像文件一样,因为他们必须提供readwriteputsprint),close和{{1} (数据是否可用?)。 因此,Linux和Windows都提供anonymous pipes。这是传统(本地)进程间通信机制之一。而且,select确实希望在两个不同的进程之间进行通信。这就是Open3.popen3将stdin,stdout和stderr连接到匿名管道的原因。

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

      这可能是你的问题:

      1. 您继续向子进程提供数据,但是您没有读取子进程写入stdout的内容。
      2. 因此,我们的子进程的输出会一直累积在缓冲区中,直到缓冲区已满。
      3. 这是操作系统暂停子进程(Open3.popen3puts块)的时候。
      4. 现在,您仍然可以将数据提供给连接到子进程的stdin的匿名管道,直到累积了太多的stdin数据。 stdin管道的缓冲区已满。然后操作系统将暂停父进程(print将阻止)。
      5. 我建议您使用stdin.writeOpen3.capture2e周围的类似包装器。您可以使用关键字参数Open3.popen3将数据传递到子流程。

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