4
2

More than 3 years have passed since last update.

実用的にRubyで外部コマンドを実行する - 車輪の再発明をしたくない人のための tty-command

Last updated at Posted at 2021-02-24

はじめに

こんにちは。(この記事は、tty-command の紹介記事です。tty-commandを知ってる人はこの記事は読まなくても大丈夫です。)

Rubyからコマンドを実行したいことは時々あると思います。よく知られている方法は、

  1. system を使う
  2. `` ← バッククオート を使う
  3. open3 を使う
  4. spawn を使う

他にも方法はありますが、私は上記の4つをよく使います。
homebrewのレシピはsystemを使って書かれていますよね。

しかし、実際には、ターミナルに

  • 実行するコマンドを出力したい
  • 結果も出力したい
  • できれば色付きされて、綺麗に整形された出力がいい!
  • 実行時間も表示したい

場合が多いのではないかと思います。車輪の再発明は、基本的に良いことだと思います。しかし、便利に使いたいだけの場合は、別に車輪、再発明しなくてもよくないか?となりますよね。そんなあなたに tty-command を紹介します。

tty-command とは?

Github: https://github.com/piotrmurach/tty-command

TTYシリーズは、Piotr Murachさんが開発しているCLIツールの作成を強力に支援するツールです。どこかで下のアイコンを見たことがある方も多いのではないでしょうか。

image.png

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 コマンドで実行することをおすすめします。

↓こんな感じで綺麗に整形された出力がリアルタイムで得られる。実行時間もバッチリ表示されている。
image.png

公式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を増やして複雑にするべきではないというのも一理あります。そのあたりは利便性とメンテナンスのバランスだと思います。

この記事は以上です。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2