Help us understand the problem. What is going on with this article?

Goでエスケープシーケンスを扱う(文字色、カーソル移動)

やりたかったこと

なんとなくコンソールに「zzz」と立体(平面)的に出力させるコマンドラインツールを作りたくなった。完成形が以下である。(Windows?知らない子ですね。。。)
sleep-go.gif

ソースについてもいたってシンプルで、単に3種類の大きさのZのAAを順に出力しているだけである。

zzz.go
package main

import (
    "fmt"
    "os"
    "time"
)

const (
    BLUE = "\033[34m"
    END  = "\033[0m"
)

func main() {
    defer fmt.Fprint(os.Stdout, END)
    fmt.Fprint(os.Stdout, BLUE)
    for i := 0; i < 5; i++ {
        for _, zzz := range zzzs {
            fmt.Fprintf(os.Stdout, "\r%s", zzz)
            time.Sleep(500 * time.Millisecond)
            fmt.Fprint(os.Stdout, "\033[13A")
        }
        fmt.Fprintln(os.Stdout, "")
        fmt.Fprint(os.Stdout, "\033[1A")
    }
}
sleeps.go
package main

var zzzs = [4]string{zzz1, zzz2, zzz3, zzz4}

const zzz1 = `







ZZZZZZZZZZ
       ZZ
     ZZ
   ZZ
 ZZ
ZZZZZZZZZZ`

const zzz2 = `




                 ZZZZZZZZZZZZZZZZ
                              ZZ
                            ZZ
ZZZZZZZZZZ                ZZ
       ZZ               ZZ
     ZZ               ZZ
   ZZ               ZZ
 ZZ               ZZ
ZZZZZZZZZZ       ZZZZZZZZZZZZZZZZ`

const zzz3 = `
                                         ZZZZZZZZZZZZZZZZZZZZZZZZ
                                                              ZZ
                                                            ZZ
                                                          ZZ
                 ZZZZZZZZZZZZZZZZ                       ZZ
                              ZZ                      ZZ
                            ZZ                      ZZ
ZZZZZZZZZZ                ZZ                      ZZ
       ZZ               ZZ                      ZZ
     ZZ               ZZ                      ZZ
   ZZ               ZZ                      ZZ
 ZZ               ZZ                      ZZ
ZZZZZZZZZZ       ZZZZZZZZZZZZZZZZ        ZZZZZZZZZZZZZZZZZZZZZZZZ`

const zzz4 = `












                                                                 `

エスケープシーケンスの操作

上記のツールを作成する上で、解決しないといけない課題は大きく分けて2つあった。

  1. 文字色の変更: 必要性があるわけではなかったが、なんとなく見栄えが良さそうなのでZのAAの文字色を変更した。
  2. カーソルの移動: 普通にZのAAを順に出力するだけでは、添付のgifにあるような同じ画面が更新され続けるような見た目にすることは出来ない。前に表示されたZのAAが残ったままである。 out-sleep-go.gif

1, 2 の問題はともにエスケープシーケンスを用いることにより解決することができ、実際のところGo特有というわけでもなく、rubyなど別の言語でも同様の手法を使える。

詳しくはエスケープシーケンスでググるといい。

1. 文字色の変更

標準出力に"\033[33m", "\033[37m" などを出力することにより、その出力後の文字色を変更できる([とmの間の数字を変更)。必ず処理終了後は文字色を戻すこと。

zzz.go(関係する部分のみ抽出)
const (
    BLUE = "\033[34m"
    END  = "\033[0m"
)

func main() {
    defer fmt.Fprint(os.Stdout, END)
    fmt.Fprint(os.Stdout, BLUE)

2. カーソルの移動

前述の通り、普通にZのAAを順に出力するだけでは、添付のgifにあるような同じ画面が更新され続けるような見た目にできず、前に表示されたZのAAが残ったままである。

これを解決するにはZのAAが出力されるたびに、ZのAAの表示位置のカーソル位置をコマンドを実行した場所まで上に移動させる必要がある。

これは、標準出力に"\033[1A", "\033[13A" などを出力することにより実現できる([とAの間に数字を変更)。Aの部分をB, C, Dと変えることによりカーソル位置を上下左右に変更できる。

zzz.go(関係する部分のみ抽出)
func main() {
    defer fmt.Fprint(os.Stdout, END)
    fmt.Fprint(os.Stdout, BLUE)
    for i := 0; i < 5; i++ {
        for _, zzz := range zzzs {
            fmt.Fprintf(os.Stdout, "\r%s", zzz)
            time.Sleep(500 * time.Millisecond)
            fmt.Fprint(os.Stdout, "\033[13A")
        }
        fmt.Fprintln(os.Stdout, "")
        fmt.Fprint(os.Stdout, "\033[1A")
    }
}

最後に

エスケープシーケンスを用いると他にも様々なことが実現でき、それを特にコマンドラインツールの作成において役立つ。

こんな画面表示をコマンドラインツールで実現したいがどうするればいいのだろうと思った際には一度エスケープシーケンスに調べてみると道が開けるかもしれない。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away