Techioz Blog

概要

mysql -u -p -h < file.sql という方法で mysql インポートを行う小さな Ruby スクリプトがありますが、これには 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

これで実際にジョブが実行されますが、見たい出力に関して気になることが 1 つあります。

最初の行を次のように変更すると、

mysqlimp = "mysql -v -u #{mysqllocal['user']} " #note the -v

そうなると、スクリプト全体が永久にハングしてしまいます。

これは、読み取りストリームと書き込みストリームが相互にブロックしているためだと思います。また、stdin が消費され続けるように stdout を定期的にフラッシュする必要があるためだと思います。言い換えれば、標準出力のバッファがいっぱいである限り、プロセスはフラッシュされるまで待機しますが、これは最初に一番下で行われるため、フラッシュされることは決して起こりません。

誰かが私の理論を検証してくれることを願っています?標準出力からすべてを出力し、標準入力にもすべてを書き込むコードを作成するにはどうすればよいでしょうか?

解決策

例:

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