ターミナルにプログレスバーを表示する方法
ターミナルに用意されている特殊な制御コード (エスケープシーケンス)を使用して、 Python, Ruby, C++, Javaなど言語に関係なく簡単にターミナルにプログレスバーを表示することができます。
仕組み
[=> ] 5.2%
プログレスバーが更新されるたびに新しい行が表示されるのを防ぐために、 プログレスバーを表示する前にカーソルを行頭に戻して行の内容を消してしまえればプログレスバーの完成です。 カーソルの制御と行の削除には
の2つを利用します。 キャリッジ・リターンは Windows の改行文字 \r\n
で出てくる \r
です。 普段 \n
に余計についてくる邪魔な文字程度の認識しかないと思いますが、単体で利用するとカーソルの位置を行頭に移動します。
移動した後文字を出力するとすでに表示されている文字を上書きして新しい文字を表示します。
例えば "abcdefg\r123"
のような文字列を出力すると \r
でカーソルを行頭に戻した後 abc
を 123
で上書きするので 123defg
が表示されます。
そのためプログレスバーの長さが短くならないなら実はキャリッジ・リターンだけで十分で、エスケープシーケンスは使わなくても問題ありません。
次にエスケープシーケンス。GUIの時代になって馴染みがないと思いますが、`<ESC>`
に続けて決められた特殊な文字列を出力することで文字の色の変更、
表示されている文字の削除などの制御することができます。 <ESC> は
ASCII文字コード の十進数で 27
のコードです (16進数で 1b
, 8進数で 33
)。 この文字に続けて [K
や
[94m
などの文字列を与えることでターミナルを制御することができます
(エスケープシーケンスの一覧)。
行の削除のエスケープシーケンスは <ESC>[94m です。
実装
[======> ] 42.3%
基本的には \r
と <ESC>[94m を標準出力に書き出して、その後に
"[====> ] 70%"
のようなプログレスバーを表す文字列を書くだけです。
ただし以下の点に気をつけて下さい
-
プログレスバーの文字列の後で改行しない。
- 例えば、Python では
print
ではなくてsys.stdout.write
を使わなくてはならないし、 Javaではprintln
ではなくてprint
を使わなくてはなりません。
- 例えば、Python では
- ファイルオブジェクトへの書き込みはバッファリングされてしまい、即座にターミナルに書き込まれない場合があるので プログレスバーを出力したらバッファをフラッシュする。
これでそれっぽくプログレスバーが表示できるようになります (というか多くの解説がそこまでしか触れてない) が、 人に公開するようなプログラムなら以下の点も留意すべきです。
- 標準出力ではなくて標準エラーに出力する。
- リダイレクトなどによって、出力先がファイルなどターミナル以外になっている場合はプログレスバーを表示しない。 そうしないとリダイレクト先のファイルがプログレスバーと制御文字で埋め尽くされて読むときに困ります。
Python
sys.stderr.write('\r\033[K' + get_progressbar_str(progress))
sys.stderr.flush()
おまけ
[===========> ] 89.7%
色をつける
プログレスバーがカラフルだと楽しいので余裕があったら色を付けてみましょう。
エスケープシーケンスを使って文字の色と背景色が制御できる
のでこれを使います。 色の制御に使うエスケープシーケンスのフォーマットは
<ESC>[n1;n2;n3;...]m です。 n1, n2, ... の部分には Wikipedia の SGR
(Select Graphic Rendition) parameters
表
にある数字が入ります。例えば、文字を明るい赤にするには <ESC> [1;31m
を出力します。設定をリセットするのは 0
なので <ESC> [0m]
を出力することで元に戻ります。例えば次の命令を Python
で実行すると真ん中の *
のみが明るい赤で表示されるはずです。
いろいろ自分で試して遊んでみて下さい。
print "*\x1b[1;31m*\x1b[0m*"
エスケープシーケンスで指定できる Red, Blue などの色が実際のターミナルでどのような色になるかは、 Wikipedia にあるエスケープシーケンスで指定する色とターミナル上での色の対応表 が分かりやすいので参考にして下さい(ターミナル毎に微妙に色合いが異なります)。