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

CUIで動くプログレスバーを作ろう

久保田です。

今日は小ネタです。

エンジニアの方々は、おそらく毎日コマンドラインを使っていると思います。
そして、コマンドラインを使っているなら、このような表示をwgetやdockerなど様々なところで見かけると思います。

f:id:AdwaysEngineerBlog:20170310170213g:plain

僕はエンジニアになりたての頃、このプログレスバーがどのように作られているかふわっと疑問に思っていました。
普通にコンソールにログを出したらこうはならないじゃないか、と
一行ずつ表示しちゃうじゃないか、と

でもある日 キャリッジリターン だと気がつきました。
通常改行時に\nとセットで使う\r、これを使います。

\rだけで使用すると、カーソル行頭復帰するので、
出力 -> キャリッジリターン -> 出力 -> キャリッジリターン -> 出力 を繰り返せばよかったのです。

歴史的には、以下のような感じらしいです。(wikipediaより)

キャリッジ・リターン(carriage return)は本来、テレタイプ端末の Baudot Code における制御文字を指す用語で、行末から行頭に戻す復帰コードであって、改行コードを含まない。その後、タイプライターで一行打ち込んだ後で紙を固定するシリンダー(キャリッジ)を次の行の先頭にタイプできるように戻し(リターン)改行する機構(またはその機構を操作するレバー)を「キャリッジ・リターン」と呼んだ。

というわけで、goで実装しました。

今回は例なので、
- 100%になるまでランダムに計算を繰り返し、%に合うだけ=を出力 & キャリッジリターンする。
- 今回はランダムで数字を与え続け、30になるまで繰り返す。
という流れです。

package main

import (
    "fmt"
    "math/rand"
    "strings"
    "time"
)

type ProgressBar struct {
    max         int
    progress int
    percent   float64
}

func main() {
        // 乱数生成器作成
    rand.Seed(time.Now().UnixNano())
        // プログレスバーを表す構造体
    progressBar := ProgressBar{max: 30, progress: 0}

    for {
        // 加算処理
        randNum := rand.Intn(10)
        progressBar.progress += int(randNum)

         // max値を超えないようにする
        if progressBar.progress > progressBar.max {
            progressBar.progress -= (progressBar.progress - progressBar.max)
        }

        // プログレスのパーセンテージ
        progressBar.percent = (float64(progressBar.progress) / float64(progressBar.max)) * 100
                // いくつ=を表示するかを決めるため、intに変換
        roundPercent := int(progressBar.percent)
                // ===> のように >付きで表示したいので、-1する。
        if roundPercent > 0 {
            roundPercent -= 1
        }
     // .も合わせて最大5桁、小数点は1桁で出力。
        fmt.Print(fmt.Sprintf("%5.1f%% [%s>%s] %d \r", progressBar.percent, strings.Repeat("=", roundPercent), strings.Repeat(" ", 99-roundPercent), progressBar.progress))

        if progressBar.progress == progressBar.max {
            fmt.Println("")
            break
        }

        time.Sleep(1 * time.Second)
    }
}

こんな感じです。

f:id:AdwaysEngineerBlog:20170310170033g:plain

今回は簡単な例ですが、 このような出力の仕組みを利用すると、CUIで人を驚かせられるような面白い遊びができるので、ぜひ試してみてください。
(表示上は消えているように見せたり、動いているように見せたりできます。)