2012年9月20日木曜日

Process::spawnで返ってくるpid

アイオーエススィックス!

Ruby の Process::spawn で返ってくる pid でハマったので、後世にこのことを残してから死のうと思いました。

はまる原因はこの仕様にあった。 http://doc.ruby-lang.org/ja/1.9.3/method/Kernel/m/spawn.html

この形式では command が shell のメタ文字
* ? {} [] <> () ~ & | \ $ ; ' ` " \n
を含む場合、shell 経由で実行されます。 そうでなければインタプリタから直接実行されます。

なるほど。
たとえばレコーダーでrecpt1をspawnするとき

 
pid = Process::spawn("recpt1 --b25 --strip 20 1800 20.ts")
 
だと、これは recpt1 のプロセスIDが返ってくるけど、実際プログラムだとだいたい変数にはいっていて、ファイル名なんかは番組名でつけちゃったりしてナニかあると困るのでクォテーションするわけで、
 
pid = Process::spawn("recpt1 --b25 --strip #{channel} #{duration} '#{filename}'")
 

こうすると、コマンドは shell 経由での実行になり pid は sh -c のものになっている。
んで、あとで recpt1ctl --pid で pid 指定してコントロールしようとしても上手く行かない。
まぁ、シグナル送るだけならプロセスグループ指定して pid を負値にすればいい

 
# 途中で終了させる例
pid = Process::spawn("recpt1 --b25 --strip #{channel} #{duration} '#{filenam}'", :pgroup => true)
...
Process::kill(:INT, -pid)
 

のだけれど、動的に録画時間伸ばしたいぜっていうときは recpt1ctl 使うのでちょっと困る。
幸い recpt1 は自分の pid を stderr に出力してくれるので、こんなふうにするといいかもしれない。

#!/usr/bin/ruby1.9.1

channel  = 20
duration = 60
filename = "hoge.ts"
pt1pid   = nil

err, out = IO::pipe
pid = Process::spawn("recpt1 --b25 --strip #{channel} #{duration} '#{filename}'", :err => out)
puts "pid: #{pid}"

loop do
  ret = IO::select([err], nil, nil, 5)
  if ret
    line = err.gets
    if /pid = (\d+)/ =~ line
      puts "recpt1 pid: #{$1}"
      pt1pid = $1
      break
    end
  else
    # タイムアウト
    break
  end
end

out.close

if pt1pid
  system("recpt1ctl --pid #{pt1pid} --extend 60") # 1分伸ばす
  Process::waitpid(pid)
else
  puts "recpt1 is seems not running"
end

実行結果

% ./ptest
pid: 14781
recpt1 pid: 14783
Pid = 14783
Extend 60 sec
%

ioいるならまぁ IO::popen でもいいけど。

あと recpt1 を & で起動して echo $! を参照するという技もあるけど sh -c がすぐ終了して waitpid が使えなくなるので、recpt1 の終了を検知するのが面倒になる。でもなんか方法あるかもしんないけどハラ減った。

結局のところ、spawn の引数に気をつければいいだけなんで、こんなバッドノウハウ的なことやらんでも次のコードで十分。

#!/usr/bin/ruby1.9.1
require 'tempfile'
require 'fileutils'

channel  = 20
duration = 60
filename = "hoge.ts"
temp     = Tempfile.new(nil, '.')

pid = Process::spawn("recpt1 --b25 --strip #{channel} #{duration} #{temp}", :err => "/dev/null")
Process::waitpid(pid)
FileUtils::mv(temp, filename)

まぁプロセスが何を録画しているのかわからなくなるけど、そのへんは工夫次第。

0 件のコメント:

コメントを投稿