Go言語で標準出力を上書きする方法(プログレスバーなど)
コマンドラインツールを作るとき、進捗状況をリアルタイムで表示したい。
Ubuntu の例ではこういう感じ。
ソースコード
上記の Go 言語でのサンプルコードは以下の通り。
タイマーとか使うのが面倒だったので、適当に長めのループを回して、全体の p % に達したタイミングで標準出力の表示を上書きしている。おまけに |
記号がグルグル回転する簡単なアニメーションも作ってみた。
package main import "fmt" var marks = []string{"|", "/", "-", "\\"} func mark(i int) string { return marks[i%4] } func main() { cnt := 5000000000 for i := 1; i <= cnt; i++ { if i%(cnt/100) == 0 { p := i / (cnt / 100) fmt.Printf("\rLoading: %s %2d%%", mark(p), p) } } fmt.Println("\nDone.") }
ポイントは fmt.Printf("\rLoading: %s %2d%%", mark(p), p)
という部分。
\r
によって "キャリッジリターン" を出力している。
キャリッジリターン
キャリッジリターン (carriage return, CR) は制御文字の1つであり「カーソルを行頭に移動させる」という意味を持つ。Go 言語を含め、多くのプログラミング言語でエスケープ文字 \r
によってキャリッジリターンが表現される。
標準出力とターミナルの挙動
標準出力 (stdout) はストリームである。ストリームにはデータが送られるのみで、すでに送ったデータが削除されることはない。ターミナル上の表示では文字データが上書きされているように見えるが、文字データの削除や更新が行われているわけではなく、ただ文字データが次々に送られているだけである。
今回の例では、Linux のターミナルが、キャリッジリターンを受け取ったときに、カーソルを行頭まで戻しているため、文字データが上書きされているように見えている。
実際、先ほどのプログラムの実行結果を log.txt
にリダイレクトし、そのファイルの中身を xxd コマンドで見てみる。
$ go run main.go > log.txt $ xxd log.txt | head 00000000: 0d4c 6f61 6469 6e67 3a20 2f20 2031 250d .Loading: / 1%. 00000010: 4c6f 6164 696e 673a 202d 2020 3225 0d4c Loading: - 2%.L 00000020: 6f61 6469 6e67 3a20 5c20 2033 250d 4c6f oading: \ 3%.Lo 00000030: 6164 696e 673a 207c 2020 3425 0d4c 6f61 ading: | 4%.Loa 00000040: 6469 6e67 3a20 2f20 2035 250d 4c6f 6164 ding: / 5%.Load 00000050: 696e 673a 202d 2020 3625 0d4c 6f61 6469 ing: - 6%.Loadi 00000060: 6e67 3a20 5c20 2037 250d 4c6f 6164 696e ng: \ 7%.Loadin 00000070: 673a 207c 2020 3825 0d4c 6f61 6469 6e67 g: | 8%.Loading 00000080: 3a20 2f20 2039 250d 4c6f 6164 696e 673a : / 9%.Loading: 00000090: 202d 2031 3025 0d4c 6f61 6469 6e67 3a20 - 10%.Loading:
標準出力に送られたすべての文字データがちゃんと残っていることが確認できる。
ちなみに、Unicode ではキャリッジリターンは 16 進数で 0d
であり,この文字も一定間隔で出現していることが分かる。