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



我有一个小ruby脚本,它以mysql -u <user> -p<pass> -h <host> <db> < file.sql的方式导入mysql,但使用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,以及?

谢谢!

  • 因为你只写标准输出,你可以简单地使用Open3#popen2e,它将stdoutstderr合并成一个流。要将以换行符结尾的字符串写入流,你可以像在一个简单的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启动进程,该进程都会从父进程继承标准输入、标准输出和标准错误。这意味着,如果您的命令行运行在终端中,则新进程的stdin, stdout和stderr将连接到终端。

另一方面,

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

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

  1. 父进程需要一些东西来排队子进程从stdin读取时应该获得的数据。
  2. 子进程需要像stdin那样提供read函数的东西。
对于stdout和stderr,我们需要类似的东西:
  1. 子进程需要写入对象。putsprint应该将父进程应该读取的数据排队。
  2. 父进程需要提供read函数的东西,以便获得子进程的标准输出和标准错误数据。

这意味着,对于标准输入、标准输出和标准输入,我们需要三个队列(FIFO)用于父进程和子进程之间的通信。这些队列的行为有点像文件,因为它们必须提供read, write(用于putsprint), closeselect(数据可用吗?)。因此,Linux和Windows都提供匿名管道。这是一种传统的(本地)进程间通信机制。Open3.popen3实际上想要在两个不同的进程之间进行通信。这就是为什么Open3.popen3将stdin, stdout和stderr连接到匿名管道。

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

这可能是你的问题:

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

我建议您在Open3.popen3周围使用Open3.capture2e或类似的包装器。您可以使用关键字参数:stdin_data将数据传递给子进程。

如果你坚持与子进程"交互"通信,你需要学习IO.select或使用多线程。这两个都是相当大的挑战。最好使用Open3.capture*

相关内容

  • 没有找到相关文章

最新更新