私は普段、テストツールとしてrubyを使っているわけですが、その中で非同期に外部プログラムを起動したり、落としたりしたいことがあります。
windows の標準コマンドを使用するならstart
コマンドととtasklist
を駆使して起動を制御し、taskkill
によって何とか終了させることができます。
Windows標準コマンドで頑張る
def pids(pname)
ret = `tasklist /fi "imagename eq #{pname}"`
lines = ret.split("\n")
ids = []
if(lines.size >= 3)
lines[2..-1].each do |line|
ids << line.split[1]
end
end
ids
end
prev = pids("ruby.exe")
system('start "" ruby -e "puts 100; loop { sleep(100) }"')
current = nil
loop do
current = pids("ruby.exe")
if current.size - prev.size == 0
sleep(1)
next
end
break
end
puts "process started!!"
cur_pid = (current - prev).first
`taskkill /f /pid #{cur_pid}`
puts "process killed!!"
かなり力業ですが、こんな感じでしょうか。start "" <外部コマンド>
とすることで別cmdが起動して非同期に実行できます。ただ、同じ名前のコマンドが実行されていた場合も考慮して、事前にpidを保持したり、tasklistを起動が完了するまで何度も実行していたりと、無駄が多いですし、何より遅い。もっといえば、差分をとったタイミングで別のプロセスが終了したりした時点で上記のコードは動かなくなるので、場合によっては使い物にならないですね。
Kernel.#spawnを使う
ruby は プロセスを扱うためのクラスが用意されていて、幸いにもWindowsでも動作します。外部コマンドを実行する方法は以下の記事が参考になります。
今回は、上記の記事に記載されていないメソッド Kernel.#spawn
を使用します。
pid = spawn('ruby -e "puts 100; loop { sleep(100) }"')
`taskkill /f /pid #{pid}`
かなりスッキリしましたね。戻り値がpidなので、起動を待ち合わせる必要もなくなりました。ただ、このままだと入出力が親のそれと共有してしまうので、親側で標準出力になにか出力させつつ、子側でも出力してしまうと体裁が崩れてしまう可能性はあります。その場合はリダイレクトを行うことで回避できるかもしれません。
pid = spawn('ruby -e "puts 100; loop { sleep(100) }"', :in => "NUL", :out => "NUL", :err => "NUL")
上記の例では、すべてを闇に葬ってますが、ファイルに落としたり、別の入出力とつなげたりできるので、用途に合わせて使い分けるのがいいと思います。詳しくは、リファレンスマニュアル参照ということで。
Process.kill
rubyのメソッドを使用して非同期での起動はできました。終了もrubyのメソッドを使用して行うことが可能です。
pid = spawn('ruby -e "puts 100; loop { sleep(100) }"', :in => "NUL", :out => "NUL", :err => "NUL")
Process.kill("KILL", pid)
ただ、WindowsでのこのメソッドはLinux系のそれよりも機能が制限されているようで、"TERM"シグナルの送信ができません。よって、"KILL"で強制終了させるしかないようです。それが嫌なら、taskkill
を使うほかないようです。
rubyで非同期に実行した外部プログラムのプロセスIDを取得し、強制終了させたい。
bamchoh/kick, bamchoh/krsp の紹介(供養)
spawn
がリダイレクトできることを知る前までは、一番初めに紹介した方法で今まで非同期実行を行ってきていたわけなんですが、それに嫌気がさして、golang
でspawnとkillのようなコマンドを作りました。(その数時間後にはspawn/Process.killでできることに気付いて泣きそうになったのは秘密)
kick
はその後に続く引数の最初をコマンドとみなして、コマンドを非同期実行します。標準出力は親とは共有せず、標準エラーのみ共有させています。
krsp
は与えられた引数のpidを KILLシグナルで強制終了させるものです。
rubyスクリプトでは上記の方法で記述すれば問題ないので、活用方法がないけど、バッチファイル等であれば、陽の目を見る日もあるかもしれない。。。
まとめ
Rubyを使って、Windowsで外部プログラムを非同期に起動/終了する方法をご紹介しました。
Windows では start/tasklist で非同期実行が可能ですが、懸念点が多いので、rubyの標準ライブラリであるKill.#spawnやProcess.killを使いましょう。Kill.#spawnはリダイレクトもできるので、状況に応じて使い分けましょう。Process.killは強制終了させるシグナルが送信されるので、正常終了が必要な場合はtaskkillを使うのが無難だと思います。
本記事に関して何があればお気軽に編集リクエストなりコメントなりしてください、できる限り対応します!
kick/krspも使いどころがあればお使いください。