はじめに
こんにちは。(この記事は、tty-command の紹介記事です。tty-commandを知ってる人はこの記事は読まなくても大丈夫です。)
Rubyからコマンドを実行したいことは時々あると思います。よく知られている方法は、
-
system
を使う -
`` ← バッククオート
を使う -
open3
を使う -
spawn
を使う
他にも方法はありますが、私は上記の4つをよく使います。
homebrewのレシピはsystem
を使って書かれていますよね。
しかし、実際には、ターミナルに
- 実行するコマンドを出力したい
- 結果も出力したい
- できれば色付きされて、綺麗に整形された出力がいい!
- 実行時間も表示したい
場合が多いのではないかと思います。車輪の再発明は、基本的に良いことだと思います。しかし、便利に使いたいだけの場合は、別に車輪、再発明しなくてもよくないか?となりますよね。そんなあなたに tty-command
を紹介します。
tty-command とは?
Github: https://github.com/piotrmurach/tty-command
TTYシリーズは、Piotr Murachさんが開発しているCLIツールの作成を強力に支援するツールです。どこかで下のアイコンを見たことがある方も多いのではないでしょうか。
TTYは好みが分かれると思いますが、私は好きです。tty-commandは、TTYシリーズのうちの一つの、Rubyの外部コマンドを実行するツールです。TTYシリーズは、独立したコンポーネントの集合体ですので、tty-commandだけ使って他のコンポーネントは一切使わないことができます。(あと、たまに誤解している人が居る気がしますが、TTYシリーズはThorと一緒に使う必要はありません。Thorを使わずOptParseだけで組んだCLIツールでもTTYは便利に使えます。)
そんなこともあってTTYシリーズが苦手な方でも、tty-command の単体利用は十分に恩恵があるのではないかなと思います。
使い方は簡単です。これまで system
と書いていたのを、 cmd.run
に変えるだけです。
gem install tty-command
例えば、gem update
gem cleanup
を実行する時はこんな感じです。
require "tty-command"
cmd = TTY::Command.new
cmd.run "gem update"
cmd.run "gem cleanup"
上記のようなrubyスクリプトを実行すると、ローカルのrubygemが更新されます。tty-commandの威力を実感するために、irbやpryではなく、小さなスクリプトファイルを作成して、ruby
コマンドで実行することをおすすめします。
↓こんな感じで綺麗に整形された出力がリアルタイムで得られる。実行時間もバッチリ表示されている。
公式READMEを見ると、主な機能はざっと網羅されていることがわかります。
以下はREADMEのコピペです。
標準出力と標準エラー出力
out, err = cmd.run("date")
プログレスバー
cmd = TTY::Command.new(printer: :progress)
ロガー
logger = Logger.new("dev.log")
cmd = TTY::Command.new(output: logger)
環境変数
cmd.run({"RAILS_ENV" => "PRODUCTION"}, :rails, "server")
リダイレクト
cmd.run(:ls, :err => :out)
cmd.run(:ls, :stderr => :stdout)
cmd.run(:ls, 2 => 1)
cmd.run(:ls, STDERR => :out)
cmd.run(:ls, STDERR => STDOUT)
cmd.run(:cat, :in => "file")
cmd.run(:cat, :in => open("/etc/passwd"))
cmd.run(:ls, :out => "log")
cmd.run(:ls, :out => "/dev/null")
cmd.run(:ls, :out => "out.log", :err => "err.log")
cmd.run(:ls, [:out, :err] => "log")
cmd.run("ls 1>&2", :err => "log")
失敗したら次のコマンドを実行
if cmd.run!("which xyzzy").failure?
cmd.run("brew install xyzzy")
end
ユーザー、グループを指定
cmd.run(:echo, "hello", user: "piotr")
cmd.run(:echo, "hello", group: "devs")
コマンドの実行結果の確認
result = cmd.run(:echo, "Hello")
result.out
result.err
result.success?
result.failure?
result.exited?
result.complete?
実際の例
cmd = TTY::Command.new
# dependencies
cmd.run "apt-get -y install build-essential checkinstall"
# fetch ruby if necessary
if !File.exists?("ruby-2.3.0.tar.gz")
puts "Downloading..."
cmd.run "wget http://ftp.ruby-lang.org/pub/ruby/2.3/ruby-2.3.0.tar.gz"
cmd.run "tar xvzf ruby-2.3.0.tar.gz"
end
# now install
Dir.chdir("ruby-2.3.0") do
puts "Building..."
cmd.run "./configure --prefix=/usr/local"
cmd.run "make"
end
カスタムプリンタ(出力の形式や色をカスタマイズすることも可能)
CustomPrinter < TTY::Command::Printers::Abstract
def write(cmd, message)
puts cmd.to_command + message
end
end
cmd = TTY::Command.new(printer: CustomPrinter)
すごいですね。だいたいの機能があります。(よかった、車輪の再発明をする手間が省けたよ!)
しかし、この手のサードパーティ製のツールは、メンテナンス状況が気になるところだと思います。
TTYシリーズは一定の利用者を持ち、少なくとも数年にわたるメンテナンスが続けられてきた実績があります。なので自分で下手な車輪を再発明するよりは安全かな?と個人的には思います。自分でコードを書いた場合も数年後には読めなくなる可能性はありますからね。
とはいえ system
を使った単純なコードは、15年後でも問題なく動作するかも知れません。不用意にgemを増やして複雑にするべきではないというのも一理あります。そのあたりは利便性とメンテナンスのバランスだと思います。
この記事は以上です。