Ruby—;Open3.popen3/如何打印输出

Ruby—;Open3.popen3/如何打印输出,ruby,popen3,Ruby,Popen3,我有一个小ruby脚本,它以如下方式执行mysql导入:mysql-u-p-h

我有一个小ruby脚本,它以如下方式执行
mysql
导入:
mysql-u-p-h
,但使用
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
然后,整个脚本将永远挂起

我猜,这是因为读和写流互相阻塞,我也猜
stdout
需要定期刷新,以便
stdin
将继续被消耗。换句话说,只要
stdout
的缓冲区已满,进程将等待刷新,但由于这是在最底层首先完成的,因此永远不会发生

我希望有人能证实我的理论?我怎样才能编写这样的代码,既能打印出
stdout
中的所有内容,又能将所有内容写入
stdin

提前谢谢

  • 由于您只向标准输出写入,因此只需使用
    Open3#popen2e
    即可将
    标准输出
    stderr
    合并为一个流
  • 要将以换行符结尾的字符串写入流,可以像在简单的hello world程序中使用
    $stdout
    一样使用
    put
  • 必须使用
    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
需要“open3”
mysqldump=#。。。
mysqlimp=“mysql-u{mysqllocal['user']}”

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

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

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

  • 父进程需要一些东西将子进程从stdin读取时应该获取的数据排队
  • 子流程需要像stdin一样提供
    read
    函数的东西
  • 对于stdout和stderr,我们需要类似的东西:

  • 子流程需要写入一些内容
    put
    print
    应该将父进程应该读取的数据排队
  • 父进程需要提供一个
    read
    函数的东西来获取子进程的stdout和stderr数据
  • 这意味着,对于stdin、stdout和stderr,我们需要三个队列(FIFO)来进行父进程和子进程之间的通信。这些队列必须有点像文件,因为它们必须提供
    读取
    写入
    (对于
    放入
    打印
    ),
    关闭
    选择
    (数据可用吗?)。 因此,Linux和Windows都提供。这是传统的(本地)进程间通信机制之一。而且,
    Open3.popen3
    确实希望在两个不同的进程之间进行通信。这就是为什么
    Open3.popen3
    将stdin、stdout和stderr连接到匿名管道

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

    这可能是您的问题:

  • 您不断地向子进程提供数据,但不读取子进程写入标准输出的内容
  • 因此,子流程的输出在缓冲区中不断累积,直到缓冲区满为止
  • 这是操作系统挂起子进程(
    put
    print
    块)的时候
  • 现在,您仍然可以将数据馈送到连接到子流程的stdin的匿名管道,直到积累了太多的stdin数据。标准管道的缓冲区满了。然后操作系统将挂起父进程(
    stdin.write
    将阻塞)
  • 我建议您使用
    Open3.capture2e
    或类似的
    Open3.popen3
    包装器。您可以使用关键字参数
    :stdin\u data
    将数据传递给子流程


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

    非常感谢你的回答!关于它的一个问题,在第1行:»[…]只写到stdout[…]«,你的意思是»stdin«,还是我在这里出错了?不,我想说的是,你取子进程(mysql)的stdout和stderr,然后写到ruby的stdout,如果你知道我的意思的话;-)啊,好的!这是有道理的!所以再次感谢你,我会立即试一试!就这样!很好!假设整个过程挂起是因为
    stout
    从未刷新,还是有其他原因?老实说,我不知道:)
    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