お久しぶりです。
ちょっと前までプロビジョニングとかImmutableInfrastructureにハマっていた国平です。
今回は、Rubyの超小ネタをご紹介します。
バッチ処理を動かしてる時って、進捗度を標準出力に出したいですよね。
そんな時に役立つ小ネタがこれです。
とにかく、何も言わずに↓のスクリーンショットを見て下さい。
分かりますか?
標準出力に、進捗を表示しているのですが、処理の進捗に合わせて出力した内容を書き換えてます。
こういう表示ができると、スクリプトがプロっぽくなりますよね。
この出力を行う方法をご紹介します。
種明かし
やっていることは、とても単純で、
- 標準出力に文字を出す
- カーソル位置を現在行の先頭に戻す
- 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ではカーソルを移動させることで、標準出力に出した内容を書き換えることができます。
この機能を利用することで、RubyでCLIスクリプトを書くときに、リッチっぽい表示ができるようになります。
なお、この内容はWindows環境で動作するかは未確認です。(追記:Windowsでも動作しました) 試す時は、Vagrantなどを使って試してみることをおススメします。