【问题标题】:Ruby—Open3.popen3 / how to print the outputRuby—Open3.popen3 / 如何打印输出
【发布时间】:2014-10-09 03:24:50
【问题描述】:

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

提前谢谢!

【问题讨论】:

    标签: ruby popen3


    【解决方案1】:
    • 由于您只写入标准输出,您可以简单地使用Open3#popen2e,它将stdoutstderr 合并为一个流。
    • 要将换行符终止的字符串写入流,您可以使用 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 行:»[...]only write to stdout[...]«,你的意思是 »stdin«,还是我这里有什么问题?
    • 不,我想说的是,如果您知道我的意思,您将子进程(mysql)的 stdout 和 stderr 写入 ruby​​ 的 stdout ;-)
    • 啊好吧!这就说得通了!所以再次感谢,会尝试一下!
    • 就是这样!效果很好!由于 stout 从未刷新过而导致整个进程挂起的假设是否正确,还是有其他原因?
    • 老实说,我不知道 :)
    【解决方案2】:

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

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

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

    1. 父进程需要一些东西来将子进程从标准输入读取时应该获取的数据排入队列。
    2. 子进程需要像标准输入一样提供read 函数的东西。

    对于标准输出和标准错误,我们需要类似的东西:

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

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

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

    这可能是你的问题:

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

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

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

    【讨论】:

      猜你喜欢
      • 2023-04-07
      • 2013-02-08
      • 2017-02-12
      • 2016-05-31
      • 1970-01-01
      • 2015-10-22
      • 2015-06-25
      • 2016-12-25
      • 1970-01-01
      相关资源
      最近更新 更多