FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

Rubyで標準出力を書き換える

お久しぶりです。

ちょっと前までプロビジョニングとかImmutableInfrastructureにハマっていた国平です。

今回は、Rubyの超小ネタをご紹介します。

バッチ処理を動かしてる時って、進捗度を標準出力に出したいですよね。

そんな時に役立つ小ネタがこれです。

とにかく、何も言わずに↓のスクリーンショットを見て下さい。

分かりますか?

標準出力に、進捗を表示しているのですが、処理の進捗に合わせて出力した内容を書き換えてます。

こういう表示ができると、スクリプトがプロっぽくなりますよね。

この出力を行う方法をご紹介します。

種明かし

やっていることは、とても単純で、

  1. 標準出力に文字を出す
  2. カーソル位置を現在行の先頭に戻す
  3. 1に戻る

この3ステップです。

で、今回一番のミソが ステップ2 ですが、Rubyでカーソルを左に動かすには、

# n文字 "左" に動かす時
printf "\e[#{n}D"
STDOUT.flush

で、OKです。 1文字動かすなら、↓ですね。

# 1文字 "左" に動かす時
printf "\e[1D"
STDOUT.flush

当然、左だけでなく上下左右に移動することが出来ます。

# n文字 上に動かす
printf "\e[#{n}A"
STDOUT.flush

# n文字 下に動かす
printf "\e[{n}B"
STDOUT.flush

# n文字 右に動かす
printf "\e[{n}C"
STDOUT.flush

# n文字 左に動かす
printf "\e[{n}D"
STDOUT.flush

というわけで、スクリーンショットのソースはこれ↓

(1..10).each do |count|
  str = "-----#{count}/10-----"
  printf str
  printf "\e#{str.size}D"
  STDOUT.flush

  sleep 0.5
end

One more thing…

おまけで、今度は逆に減っていくパターンを考えます。

今度は、最初に”#”を10個表示して、どんどん減っていくような表示をさせます。

rewrite_decrement.rb
(1 .. 10).to_a.reverse.each do |count|
  str = ""
  # "#"をcount個つなげる
  count.times { str += "#" }

  printf str
  printf "\e[#{str.size}D"
  STDOUT.flush

  sleep 0.5
end

↑このコードを実行すると…

なにも表示が変わりません…orz これは、最初に”#”が10個表示されて、その上に更に”#”を9個,8個,7…と書き換えて行っているので、最初に表示された後ろの方の”#”がそのまま残ってしまっています。 なので、ちゃんと上書きするような実装に書き換えます。

(1 .. 10).to_a.reverse.each do |count|
  str = ""
  count.times { str += "#" }
  # 後ろはスペースで埋める
  (10 - count).times {str += " "}

  printf str
  printf "\e[#{str.size}D"
  STDOUT.flush

  sleep 0.5
end

実行すると今度は上手く”#”が減っていきます。

まとめ

Rubyではカーソルを移動させることで、標準出力に出した内容を書き換えることができます。

この機能を利用することで、RubyCLIスクリプトを書くときに、リッチっぽい表示ができるようになります。

なお、この内容はWindows環境で動作するかは未確認です。(追記:Windowsでも動作しました) 試す時は、Vagrantなどを使って試してみることをおススメします。