読者です 読者をやめる 読者になる 読者になる

ツナワタリマイライフ

日常ネタから技術ネタ、音楽ネタまで何でも書きます。

open3でシェルコマンドを実行してラップする

はじめに

業務でcapistranoを使ってツールを作っています。

take-she12.hatenablog.com

今回仕事で要求されている手順で、サーバ1で作業して、サーバ2で作業して、またサーバ3で作業、というものがありました。これをcapistranoで実行するためにはサーバごとにtaskを実行してやらないといけないと思っています。(というのも、ロールごとにわけたとしても、同時に実行されてしまい、順番が保証されない)このいい解決策がないか、ということを探るためにもsshkitを読み込みたい。多分sshkitを単独で使えばいけるんだろうな。

上記のような課題があり、capコマンドをラップする方法を求められています。普通にシェルで書いてもいいんですが、せっかくなのでrubyで統一感を出したいと思い、open3を使って実装してみることにします。

open3

外部コマンドを実施して、標準出力、標準エラー出力、終了ステータスが得られる、とても便利。

kaihar4.com - Rubyのopen3が便利だった話

まず、サンプルのcapistranoのtaskを書きました。

deploy/local.rbにローカル定義。

server 'localhost', user: 'root', roles: %w{web}

deploy.rbにtask定義。

task :ls do
  run_locally do
    execute :ls, "-lah"
  end
end

task :uptime do
  run_locally do
    execute :uptime
  end
end

この時点でcapコマンドを実行するとこんな感じ。

MacBook-Air:capistrano take$ cap local ls
 INFO [eadb22c2] Running /usr/bin/env ls -lah on 
DEBUG [eadb22c2] Command: /usr/bin/env ls -lah
DEBUG [eadb22c2]    total 8
DEBUG [eadb22c2]    drwxr-xr-x   6 take  staff   204B  4 23 12:32 .
DEBUG [eadb22c2]    drwxr-xr-x+ 67 take  staff   2.2K  4 23 12:46 ..
DEBUG [eadb22c2]    -rw-r--r--   1 take  staff   837B  4 23 12:23 Capfile
DEBUG [eadb22c2]    drwxr-xr-x   4 take  staff   136B  4 23 12:32 config
DEBUG [eadb22c2]    drwxr-xr-x   3 take  staff   102B  4 23 12:23 lib
DEBUG [eadb22c2]    drwxr-xr-x   3 take  staff   102B  4 23 12:40 wrap
 INFO [eadb22c2] Finished in 0.020 seconds with exit status 0 (successful).
MacBook-Air:capistrano take$ cap local uptime
 INFO [7639971c] Running /usr/bin/env uptime on 
DEBUG [7639971c] Command: /usr/bin/env uptime
DEBUG [7639971c]    12:48  up 15 days, 16:58, 2 users, load averages: 0.98 1.27 1.36
 INFO [7639971c] Finished in 0.012 seconds with exit status 0 (successful).

さてこれらをラップしたり、コマンドを実行できるopen3を使ってみましょう。

require 'open3'

result = Open3.capture3('echo "hoge"')
p result

result = Open3.capture3('eho "hoge"')
p result
p result[2].class
p result[2].exitstatus
p result[2].pid

result = Open3.capture3('cap local ls')
p result

これを実行すると

MacBook-Air:capistrano take$ ruby wrap/wrapper.rb 
["hoge\n", "", #<Process::Status: pid 27482 exit 0>]
["", "sh: eho: command not found\n", #<Process::Status: pid 27483 exit 127>]
Process::Status
127
27483
[" \e[34mINFO\e[0m [\e[32m2d6060e3\e[0m] Running \e[33m\e[1m/usr/bin/env ls -lah\e[0m\e[0m on \e[34m\e[0m\n\e[30mDEBUG\e[0m [\e[32m2d6060e3\e[0m] Command: \e[34m/usr/bin/env ls -lah\e[0m\n\e[30mDEBUG\e[0m [\e[32m2d6060e3\e[0m] \e[32m\ttotal 8\n\e[0m\e[30mDEBUG\e[0m [\e[32m2d6060e3\e[0m] \e[32m\tdrwxr-xr-x   6 take  staff   204B  4 23 12:32 .\n\e[0m\e[30mDEBUG\e[0m [\e[32m2d6060e3\e[0m] \e[32m\tdrwxr-xr-x+ 67 take  staff   2.2K  4 23 12:46 ..\n\e[0m\e[30mDEBUG\e[0m [\e[32m2d6060e3\e[0m] \e[32m\t-rw-r--r--   1 take  staff   837B  4 23 12:23 Capfile\n\e[0m\e[30mDEBUG\e[0m [\e[32m2d6060e3\e[0m] \e[32m\tdrwxr-xr-x   4 take  staff   136B  4 23 12:32 config\n\e[0m\e[30mDEBUG\e[0m [\e[32m2d6060e3\e[0m] \e[32m\tdrwxr-xr-x   3 take  staff   102B  4 23 12:23 lib\n\e[0m\e[30mDEBUG\e[0m [\e[32m2d6060e3\e[0m] \e[32m\tdrwxr-xr-x   3 take  staff   102B  4 23 12:40 wrap\n\e[0m \e[34mINFO\e[0m [\e[32m2d6060e3\e[0m] Finished in 0.011 seconds with exit status 0 (\e[1m\e[32msuccessful\e[0m\e[0m).\n", "", #<Process::Status: pid 27484 exit 0>]

capコマンドの実行結果が化けちゃってますね。

出力しているとおり、caputure3の結果は終了ステータスを示すProcess::Statusクラス。はじめて知った。pidや終了コードを取り出せます。

class Process::Status (Ruby 2.2.0)

capコマンドをつなげる場合は、終了ステータスが0以外であれば処理を中断する、というロジックにすればうまくラップできそうですね!

おわりに

open3関連はまだ全然見れてないのでドキュメントを読みたい。

あとは標準出力、画面に出したいなぁと思うのは贅沢でしょうか。標準出力は画面に出しつつ、内部で保持して、終了コードで判定したりとか。実用までもう少し触ってみるかな。