我有一个小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
,它将stdout
和stderr
合并成一个流。要将以换行符结尾的字符串写入流,你可以像在一个简单的hello world程序中使用$stdout
一样使用puts
。 - 必须使用
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
无论何时从命令行或通过fork
启动进程,该进程都会从父进程继承标准输入、标准输出和标准错误。这意味着,如果您的命令行运行在终端中,则新进程的stdin, stdout和stderr将连接到终端。
Open3.popen3
不将stdin、stdout和stderr连接到终端,因为您不希望直接与用户交互。所以我们需要别的东西
对于stdin,我们需要具有两种功能的东西:
- 父进程需要一些东西来排队子进程从stdin读取时应该获得的数据。
- 子进程需要像stdin那样提供
read
函数的东西。
- 子进程需要写入对象。
puts
和print
应该将父进程应该读取的数据排队。 - 父进程需要提供
read
函数的东西,以便获得子进程的标准输出和标准错误数据。
这意味着,对于标准输入、标准输出和标准输入,我们需要三个队列(FIFO)用于父进程和子进程之间的通信。这些队列的行为有点像文件,因为它们必须提供read
, write
(用于puts
和print
), close
和select
(数据可用吗?)。因此,Linux和Windows都提供匿名管道。这是一种传统的(本地)进程间通信机制。Open3.popen3
实际上想要在两个不同的进程之间进行通信。这就是为什么Open3.popen3
将stdin, stdout和stderr连接到匿名管道。
每个管道,无论是匿名的还是命名的,都有一个有限大小的缓冲区。这个大小取决于操作系统。问题是:如果缓冲区已满,并且一个进程试图向管道写入,操作系统将挂起该进程,直到另一个进程从管道读取。
这可能是你的问题:
- 您继续向子进程提供数据,但您不读取子进程写入标准输出的内容。 因此,子进程的输出在缓冲区中不断累积,直到缓冲区满为止。
- 操作系统挂起子进程(
puts
或print
块)。 - 现在,您仍然可以将数据提供给连接到子进程标准输入的匿名管道,直到标准输入数据累积太多为止。标准输入管道的缓冲区已满。然后操作系统将挂起父进程(
stdin.write
将阻塞)。
我建议您在Open3.popen3
周围使用Open3.capture2e
或类似的包装器。您可以使用关键字参数:stdin_data
将数据传递给子进程。
如果你坚持与子进程"交互"通信,你需要学习IO.select
或使用多线程。这两个都是相当大的挑战。最好使用Open3.capture*
。