読者です 読者をやめる 読者になる 読者になる

座敷牢日誌

都落ちした元SEがソフトウェアやネット関連のことを書いています

コンソールアプリケーションで進行状況等を出力する……キャリッジ・リターンの話

色々あるコンソールアプリケーションがよくとるやり方で、現在の行の出力内容をどんどん更新していく……といった出力方法がある。例えば、wgetを実行するとダウンロード状況のパーセンテージやインジケータや残り時間が、刻々と更新表示されていく。恥ずかしながら、こういう出力の実装方法をずっと知らずにいた。標準入出力に関する深い知識と正しい理解がいるんだろうと漠然と思っていたが、いざ調べてみるとそうでもなかった。
youtube-dlのソースを暇つぶしに眺めていてわかってしまった。

出力行の先頭にエスケープシーケンス「\r」をつければ良い

出力行の出力にエスケープシーケンス「\r」をつけてやる。たったこれだけのことだった。例えば次のようなスクリプトを書いてbashで実行してみる。

#!/bin/bash
for i in `seq 5`
do
	printf "\r%s" `date "+%H:%M:%S"`
	sleep 1
done
echo

すると、スクリプト実行のプロンプトに現在時刻が表示され、1秒ごとに時刻表示が更新されていくという感じ。

% bash bash-ind
02:38:51
% 

pythonでファイルダウンロードの進行状況を出力するコードも簡単に書いてみた。

#!/usr/bin/python
# coding: utf8

import sys
import urllib

url = "http://localhost/test.flv"

res = urllib.urlopen(url)
filesize = int(res.info().get("Content-Length"))
print "filesize", filesize
print "start"
with open("test.flv", "wb") as myfile:
    getsize = 0
    while True:
        data = res.read(1024)
        getsize += len(data)
        if len(data) == 0:break
        myfile.write(data)
        sys.stdout.write("\r%s/%s" % (str(getsize), str(filesize)))

res.close()
print
print "end"

実行すると、次のような感じで「nnn/35738654」の取得済みサイズを示す「nnn」の部分が更新されていく。

% python downloader.py
start
35738654/35738654
end
% 

更新する時間の間隔を何も考慮していないのでこれだとコストが増してしまうが、1秒ごとに出力するみたいな感じで実装すればプログレスバー的なものを実装できそう。面白いことを知ったなー。自分で使ってるダウンロード関連のスクリプトを書き直してみたくなってきた。

ちなみにWindowsのVBScriptでも同じことができた。

For i = 1 To 10
    WScript.StdOut.Write VbCr & i
    WScript.Sleep 1000
Next

こんな感じで。VBScript/VBA/VB6は「\r」ではなく、VbCrという定数でエスケープシーケンスを表現する。書いてて思い出した。

あと、期待されては困るので書いておきますが、この手の状況の表示やプログレスバーの実装等は既存のライブラリを流用したり、知っているアプリケーションのソースコードを見ればわかるのであって、わざわざ自分で書き起こすメリットとかはあまりないと思います。

\rは「キャリッジ・リターン」

\rはキャリッジリターンと呼ばれる制御文字の一つ。
キャリッジ・リターン - Wikipedia
知ってはいて、改行とかでよく出てくるやつだ、「CR」とか「LF」とかの。その程度は知っている。でも、意味が「行頭へ戻す」で、こういう風に使えるということは全然知らなかった。場当たり的な対処でずっとなんとかなってきたせいか、文字コードや制御文字に関する知識はずっと曖昧なままだ。たぶん、これからも。

次に気になってくるのはページャーrogueのようなゲームのような出力を、コンソールアプリケーションで実装するやり方かな。関心があればまた調べる。

広告を非表示にする