碧色工房 -blue studio-

Total: 1834523 Today: 319 Yesterday: 1057
作成:

コンソール制御

前回は、一般的な文字列リテラルに制御コード等を記述するための エスケープシーケンスについて解説したが、これの発展形、というか、 より柔軟な指定ができ自由度の高い制御方法として、ANSIエスケープコードというものがある。 以前の記事で、簡単な一覧を紹介したことがあるが、 改めて整理して紹介してみようと思う。

そもそもエスケープシーケンスは狭義にはエスケープコードから始まるシーケンスなので、 むしろこちらのほうが本来のエスケープシーケンスというべきかもしれない。

ANSIエスケープコード

詳細な一覧は Wikipedia に英語ではあるが載っている。 できることは大まかにいうと、 コンソール上のカーソル位置の移動、出力済みテキストの消去、コンソールのスクロール、 テキストフォントの種別指定、前景色、背景色の指定、といったところになる。

いわゆる端末エミュレータと呼ばれるコンソールでこのシーケンスが解釈され、 テキスト端末であるコンソールでグラフィカルな表現を行うことができる。 ただし、対応しているかどうか、対応しているとしてもどこまで対応しているかは端末次第で、 Windowsのコマンドプロンプトでは(そのままでは)利用できない。

UNIX系の環境なら標準のターミナルエミュレータを使えばよいが、Windows環境だと Cygwin + TeraTerm + Cygterm などが手軽かもしれない。

さて、このANSIエスケープコード (更に正確には、その中のCSI (Control Sequence Introducer (Indicator))コードと呼ばれるもの) のフォーマットは以下のようになっている。

ESC[n1;n2;...X

最初のESCは表示文字でないので便宜上このように表示しているが、エスケープコード(文字コード:0x1b)である。 次が[(左角括弧)、続いて、引数が;(セミコロン)で区切られて続き、 最後にシーケンス識別文字が来る。

前回紹介したエスケープシーケンスと異なり、 ここでのエスケープの始まりは\ではなく、エスケープコードから始まる。 エスケープコードの文字コードは0x1bなので、これをエスケープシーケンスで表現すると 8進数で\033、もしくは16進数で\x1bとなる。 どちらも同じものを表現しているが、8進数を利用した\033で書かれる場合が多いようだ。

カーソル移動、及び画面制御関係のコードの一覧を次に示す。 実際にはこれ以外にもあるが、詳しくはWikipediaなどを参照のこと。

記述意味
ESC[nA カーソルを上にn移動させる。(nには整数が入る、省略すると1)
ESC[nB カーソルを下にn移動させる。(nには整数が入る、省略すると1)
ESC[nC カーソルを右にn移動させる。(nには整数が入る、省略すると1)
ESC[nD カーソルを左にn移動させる。(nには整数が入る、省略すると1)
ESC[nE カーソルをn行下の先頭に移動させる。(nには整数が入る、省略すると1)
ESC[nF カーソルをn行上の先頭に移動させる。(nには整数が入る、省略すると1)
ESC[nG カーソルを現在の横位置に関係なく左端からnの場所に移動させる。 (左端を1とする。nには整数が入る、省略すると1)
ESC[n;mH カーソルを現在の位置に関係なく上端からn、左端からmの場所に移動させる。 左上を1,1とする座標指定だが、1つ目の数字が縦位置で、2つ目の数字が横位置であることに注意 (n,mには整数が入る、省略すると1)
ESC[nJ 画面消去、nを省略、もしくは0を指定した場合、カーソルより後ろを消去、 1を指定するとカーソルより前を消去、2を指定すると全体を消去となる。
ESC[nK 行消去、nを省略、もしくは0を指定した場合、カーソルより後ろを消去、 1を指定するとカーソルより前を消去、2を指定すると行全体を消去となる。
ESC[nS n行分、コンソールを次にスクロールする。(nには整数が入る、省略すると1)
ESC[nT n行分、コンソールを前にスクロールする。(nには整数が入る、省略すると1)
ESC[n;mf ESC[n;mHと同じで、カーソルを座標移動する
ESC[nm SGR(Select Graphic Rendition)というコマンドで、グラフィカルな指示を行う、 複雑というかバリエーション豊富なので後述

次に、これらの動作を確認するためのサンプルコードを示す。 これらは動的な制御でもあり、サンプルコードの実行結果を示すのは難しいので、 いろいろと実際に試してみて欲しい。

なお、コードを書く上での注意点だが、 通常のprintfでは、改行コードが出力されるまではバッファリングされて、 実際にコンソールに出てこない場合がある。 最終的な出力が一致すれば良いということであれば特に気にする必要もないが、 多くの場合、動的な変化を見せたい場合に利用すると思うので、 このサンプルのようにstderrへ出力するか、 setbufを使って、出力バッファを無効にする、 またはfflushを使って、適切なタイミングでバッファを掃き出させる、などする必要がある。 この辺の解説はこちらを参照のこと。

fprintf(stderr,"1234567890");
sleep(1);
fprintf(stderr, "\033[5G");
sleep(1);
fprintf(stderr, "\033[K");

SGR(Select Graphic Rendition)コマンド

前の表で後述と書いた、グラフィック要素指定の部分を説明する。 フォーマットとしては、「ESC[nm」であり、nには整数が入る。 また、nの値によっては別の引数を取るものも存在する。 と、非常にバリエーション豊富なのだが、 それゆえに端末によってどこまで対応されているかもまちまちだったりする。

逐一説明してもいいのだが、まずは以下のように、 nの値を順に変化させながら出力するサンプルコードを実行してみる。 なお、後に説明するが、「ESC[0m」はそれまでの指定を無効化してもとに戻すという意味がある。

for (i = 0; i < 11; i++) {
  for (j = 0; j < 10; j++) {
    const int v = i*10+j;
    printf("\033[%dm%03d\033[0m ", v, v);
  }
  printf("\n");
}

で、実行結果だが、テキストのアウトプットを書いても仕方ないので、 実際のコンソールのスクリーンショットを掲載する。

以下が、Ubuntuのgnome-terminal(日本語環境だと「端末」となっているもの)での実行結果。

gnome-terminal @Ubuntu

以下が、Ubuntuのxtermでの実行結果。 このスクリーンショットでは005が表示されていないが、点滅表示されている。

xterm @Ubuntu

以下が、Windows上のTeraTermでの実行結果

TeraTerm @Windows8.1

いずれも実行している実行ファイルは同一で、出力されるコードも同一である。 違いが出ているのは各端末で出力されたコードの解釈のされ方や表示のされ方の違いによるものだ。 結果的にみると、指定して意味がありそうな値の範囲は「0-9」「30-49」「90-109」の間ぐらいのようだ。 次にこの範囲の指定についてまとめる。

記述意味
ESC[0m 指定をリセットし未指定状態に戻す。 SGRによって変化させたあとは、必ずこのコードを出力して他の出力に影響を与えないようにする
ESC[1m ボールド指定、フォントが対応していれば太字になる。TeraTermでは色が変化、この色は設定で指定する
ESC[2m 薄く表示する、あまり対応されていないらしいが、gnome-terminalでは文字色が変化している
ESC[3m イタリック表示、フォントが対応していればイタリック体になる
ESC[4m アンダーライン
ESC[5m ブリンク、毎分150回以下のペースで点滅する。TeraTermでは色が変化。この色は設定で指定する
ESC[6m 高速ブリンク、あまり対応されていないらしい
ESC[7m 反転、前景色と背景色を入れ替えて表示
ESC[8m 表示を隠す、gnome-terminalとxtermでは見えない。 なお、選択による反転でも見えないが、コピーアンドペーストすると、ちゃんとその文字が出力されていることが分かる
ESC[9m 取り消し、gnome-terminalでは取り消し線が表示された
ESC[30m~ESC[37m 文字色指定、色番号0~7は後述
ESC[38m 文字色指定拡張用、引き続き引数をとる。5;xで、0~255のカラーインデックス指定。 2;r;g;bでRGB指定。ただし、24bitカラーが表示できる端末は少ない
ESC[39m 文字色をデフォルトに戻す
ESC[40m~ESC[47m 背景色指定、色番号0~7は後述
ESC[48m 背景色指定拡張用、引き続き引数をとる。5;xで、0~255のカラーインデックス指定。 2;r;g;bでRGB指定。ただし、24bitカラーが表示できる端末は少ない
ESC[49m 背景色をデフォルトに戻す
ESC[90m~ESC[97m 前景色指定、30番代の指定より強い色。標準ではないらしい
ESC[100m~ESC[107m 背景色指定、40番代の指定より強い色。標準ではないらしい

色の指定であるが、0~7の色指定が使える、これはANSIカラーとも呼ばれるもので概ね以下の様な定義になる。 実際にはいわゆる純色ではなく、輝度をグレーに寄せたり、彩度を少し落としたような色が使われる。

0: Black 1: Red 2: Green 3: Yellow 4: Blue 5: Magenta 6: Cyan 7: White

これらの色は、端末エミュレータの設定などで変更できる場合も多い。

また、このSGR指定は;でつなげることができる、 例えば、\033[1;30;47mと指定すると、ボールド、文字色黒、背景白、となる

UNIX系のコマンドだと、lsなどを使った時に、 ファイルの属性などによって異なる色でテキストが表示されることがあると思うが、 あの色は、これと同様の仕組みで指定されている。

次に、色指定の8は拡張用に用意されており、 追加の引数で詳細な色指定ができるようになっている、 文字の色の場合、「ESC[38;5;xm」と指定することで 256色のカラーパレットから色を指定することができる。 その動作を確認するため以下のサンプルコードを実行してみる。

for (i = 0; i < 16; i++) {
  for (j = 0; j < 16; j++) {
    const int v = i*16+j;
    printf("\033[38;5;%dm%02X\033[0m ", v, v);
  }
  printf("\n");
}

以下が、Ubuntuのgnome-terminalでの実行結果である。

gnome-terminal @Ubuntu

サンプルコードの出力を「ESC[48;5;xm」に変更すると、背景色が同様に変化する。

gnome-terminal @Ubuntu

ここまで色が使えると、単にテキストへの付加情報として色を付ける以外の使い方もできそうに思うが、 拡張用ということで、256色への対応ができる端末エミュレータは限られてくる。 とはいえ、前述のxtermやTeraTermも対応している。