エスケープシーケンスを体感する

エスケープシーケンス(escape sequence)とは、直訳すれば「エスケープに続く数列(文字コード列)」といった意味合いになると思う。広い意味では、特別な文字表記(エスケープ記号)で始まる一連の文字列を連想してしまう。例えば、\nが改行を表現するのも広い意味ではエスケープシーケンスに含まれる。しかし、ここではもっと狭い意味のエスケープシーケンスを扱う。ASCII制御コードを拡張するためのエスケープシーケンスだ。

前回調べたように、ASCII制御コード(\x00〜\x20、\x7f)はテレタイプ端末で通信することを想定した古い規格になっている。その古いASCII制御コードだけでは、時代と共に進化してきたビデオ端末や端末エミュレータを満足に制御できるはずがない。新たな制御コードを追加する必要があったのだ!

ところが、新たな制御コードを追加するにも、たったの128文字しかないASCIIコード表には空きがない。ではどうしたかというと、ASCII制御コードのESC(\x1B)を利用して拡張したのだ。ESC(\x1B)で始まる複数の文字コードの連続によって、様々な制御機能を追加している。拡張されたASCII制御コードを体感してみる。

コード領域の基礎知識

  • ASCIIコードは7ビットの文字コードである。(\x00〜\x7F)
  • 表現できる文字数を増やすため8ビットの文字コードが考案された時、それぞれの領域を以下のように区別するようになった。
    • C0のC = Control(制御)のC
    • GLのG = Graphic(図形)のG

  • 一方、従来の7ビットコード体系から拡張されたC1領域を使いたい需要もあった。
  • そこで、制御コード ESCを利用して、以下のように指定する仕組みとなった。
    • ASCII制御コードをControlキー@ A〜Z [ \ ] ^ _で入力する仕組みと同じように、
      • 例:^@、^G、^[
    • C1領域の制御コードは制御コード ESC@ A〜Z [ \ ] ^ _で指定できる。
      • 例:\e@、\eG、\e[
      • ESC = \e = \x1B = \033
  • つまり、8ビットコード体系の\x80\x9Fは、7ビットコード体系では\x1B40\x1B5Fまでの2バイトで表現される!

ESC(\x1B)の制御コードで始まる数列なのでエスケープシーケンスと呼ばれるのだ。

C1以外の制御コード

  • さらに、ESC(\x1B)を利用することで、それに続く文字コードは何でもエスケープシーケンスと認識できる。
  • もはや、32文字のC1領域だけの表現にこだわる必要はなくなった。
  • 印字可能な94文字すべてを使って制御コードを拡張できるのだ!
ISO 2022の文字コード制御
  • 例えば、ISO 2022はC1領域以外のエスケープシーケンスを利用して必要な文字集合を切り替えている。
      • 但し、ISO 2022のエスケープシーケンスは独自に文字コードを制御したい需要でもない限り、覚える必要はなさそう。
      • ほとんどの場合、OSやエディタ・メールなどがサポートするテキスト入力環境が、良きに計らいうまく処理してくれる。
      • iconvなどの文字コード変換ライブラリを使えば、このようなエスケープシーケンスを知らなくても変換できてしまう。
      • 文字化けしてしまったメールを自動で直すコンバーターとか作りたくなったら、その時に覚えるだけで十分。

略語 意味 ESC
シーケンス
SI or LS0 GL←G0呼び出し(ロッキングシフト) ^N
SO or LS1 GL←G1呼び出し(ロッキングシフト) ^O
LS2 GL←G2呼び出し(ロッキングシフト) \en
LS3 GL←G3呼び出し(ロッキングシフト) \eo
LS1R GR←G1呼び出し(ロッキングシフト) \e~
LS2R GR←G2呼び出し(ロッキングシフト) \e}
LS3R GR←G3呼び出し(ロッキングシフト) \e
SS2 G2の1文字限りの呼び出し(シングルシフト) \eN
SS3 G3の1文字限りの呼び出し(シングルシフト) \eO
GZD4 G0←94文字集合の指示(1バイトコード \e(
G1D4 G1←94文字集合の指示(1バイトコード \e)
G2D4 G2←94文字集合の指示(1バイトコード \e*
G3D4 G3←94文字集合の指示(1バイトコード \e+
G1D6 G1←96文字集合の指示(1バイトコード \e-
G2D6 G2←96文字集合の指示(1バイトコード \e.
G3D6 G3←96文字集合の指示(1バイトコード \e/
GZDM4 G0←94のn乗文字集合の指示(複数バイトコード \e$(
G1DM4 G1←94のn乗文字集合の指示(複数バイトコード \e$)
G2DM4 G2←94のn乗文字集合の指示(複数バイトコード \e$*
G3DM4 G3←94のn乗文字集合の指示(複数バイトコード \e$+
G1DM6 G1←96のn乗文字集合の指示(複数バイトコード \e$-
G2DM6 G2←96のn乗文字集合の指示(複数バイトコード \e$.
G3DM6 G3←96のn乗文字集合の指示(複数バイトコード \e$/
S7C1T 7ビットモード。応答に7ビットシーケンスを使う。 \eSP F
S8C1T 8ビットモード。応答に8ビットシーケンスを使う。 \eSP G

カーソル・表示・キー入力制御

略語 意味 ESC
シーケンス
DECSC 保存する(カーソル位置とテキスト属性) \e7
DECRC 復元する(カーソル位置とテキスト属性) \e8
DECKPAM アプリケーションキーパッドモードに設定する \e=
DECKPNM 数値キーパッドモードに設定する \e>
RIS 端末リセット(画面クリア・カーソル左上・タブリセット) \ec
DECDHL カーソル行の表示倍率を設定(縦倍・横倍して上側を表示) \e#3
DECDHL カーソル行の表示倍率を設定(縦倍・横倍して下側を表示) \e#4
DECSWL カーソル行の表示倍率を設定(標準表示) \e#5
DECDWL カーソル行の表示倍率を設定(横倍表示) \e#6
DECALN 画面を'E'で埋める \e#8

C1領域の制御コード

  • C1領域の制御コードには、以下のような意味がある。

略語 由来語句 意味 8ビットの
16進数コード
ESC
シーケンス
PAD Padding Character \x80 \e@
HOP High Octet Preset \x81 \eA
BPH Break Permitted Here 分割許可 \x82 \eB
NBH No Break Here 分割禁止 \x83 \eC
IND Index カーソルを次の行へ移動 \x84 \eD
NEL NExt Line カーソルを次の行の先頭に移動 \x85 \eE
SSA Start of Selected Area \x86 \eF
ESA End of Selected Area \x87 \eG
HTS Horizontal Tabulation Set 現在のカーソル位置に水平タブストップを設定する \x88 \eH
HTJ Horizontal Tabulation with Justification \x89 \eI
VTS Vertical Tabulation Set 現在のカーソル位置に垂直タブストップを設定する \x8A \eJ
PLD Partial Line Down 下付き文字にする \x8B \eK
PLU Partial Line Up 上付き文字にする \x8C \eL
RI Reverse Index カーソルを前の行へ移動 \x8D \eM
SS2 Single Shift 2 GL または GR へ G2 を次の一文字に限り呼び出す \x8E \eN
SS3 Single Shift 3 GL または GR へ G3 を次の一文字に限り呼び出す \x8F \eO
DCS Device Control String DCSシーケンスを開始、STで終了 \x90 \eP
PU1 Private Use 1 \x91 \eQ
PU2 Private Use 2 \x92 \eR
STS Set Transmit State \x93 \eS
CCH Cancel Character \x94 \eT
MW Message Waiting \x95 \eU
SPA Start of Protected Area \x96 \eV
EPA End of Protected Area \x97 \eW
SOS Start Of String SOSシーケンスを開始、STで終了 \x98 \eX
SGCI Single Graphic Character Introducer \x99 \eY
SCI Single Character Introducer \x9A \eZ
CSI Control Sequence Introducer CSIシーケンスを開始 \x9B \e[
ST String Terminator DCS, SOS, OSC, PM, APC シーケンスの終端 \x9C \e\
OSC Operating System Command OSCシーケンスを開始、STで終了 \x9D \e]
PM Privacy Message PMシーケンスを開始、STで終了 \x9E \e^
APC Application Program Command APCシーケンスを開始、STで終了 \x9F \e_

シーケンスの拡張

  • C1領域の制御コードの中でもシーケンス関連のコードは、それに続く複数のコードを引数にして、さらに機能を拡張する。
DCS = \x1B50
SOS = \x1B58
CSI = \x1B5B
OSC = \x1B5D
PM  = \x1B5E
APC = \x1B5F
  • ESC(\x1B)が制御コードを拡張し、続く2文字目のコードでエスケープシーケンスをさらに拡張している。

CSI(Control Sequence Introducer)

  • 中でも画面出力をコントロールするCSIシーケンス(\e[ = \x1B5B)は重要である。
  • CSIシーケンスを使えば、カーソル位置や文字色・背景色・明るさ・下線・点滅・反転などのテキスト属性を自由に設定できる。
  • CSIシーケンスは、\e[に続けて引数終了文字を指定する。
    • 終了文字が動作を決定する。
    • 引数でその動作の詳細を指定する。


例:

CSI 引数 終了文字 意味
\e[ 2 A 2行上にカーソルを移動する
\e[ A 1行上にカーソルを移動する(デフォルト値 = 1が利用された)
\e[ 10;8 H 10行8列へカーソルを移動する( ; で区切って二つの引数を指定した)

    • 引数は10進数で指定する。
    • 引数は ; で区切って複数指定できる。(最大16個)
    • 引数を省略するとデフォルト値が利用される。
      • n行m列などの位置情報を意味する引数のデフォルト値 = 1
      • 機能を選択する引数のデフォルト値 = 0
  • すべては網羅できないが、よく使いそうなCSIシーケンスを調べてみた。
略語 意味 ESC
シーケンス
CUU カーソル移動(n行上へ) \e[nA
CUD カーソル移動(n行下へ) \e[nB
CUF カーソル移動(n文字右へ) \e[nC
CUB カーソル移動(n文字左へ) \e[nD
CNL カーソル移動(n行下の行頭へ) \e[nE
CPL カーソル移動(n行上の行頭へ) \e[nF
CHA カーソル移動(n列へ) \e[nG
CUP カーソル移動(nm列へ) \e[n;mH
CHT カーソル移動(カーソル行のn個次のタブ位置へ) \e[nI
CBT カーソル移動(カーソル行のn個前のタブ位置へ) \e[nZ
ED テキストクリア(カーソル位置から画面末尾まで) \e[J
ED テキストクリア(カーソル位置から画面先頭まで) \e[1J
ED テキストクリア(画面全体) \e[2J
EL テキストクリア(カーソル位置から行末まで) \e[K
EL テキストクリア(カーソル位置から行頭まで) \e[1K
EL テキストクリア(1行全体) \e[2K
ECH テキストクリア(カーソル位置右側をn文字、SPに置き換え) \e[nX
DCH テキスト削除(カーソル位置右側をn文字、左にスライド) \e[nP
IL n行挿入 \e[nL
DL n行削除 \e[nM
TBC タブ削除(カーソル位置のタブ) \e[g
TBC タブ削除(すべてのタブ) \e[3g
DECSTBM スクロール範囲を設定(画面全体) \e[r
DECSTBM スクロール範囲を設定(n行からm行まで) \e[n;mr
SU スクロールする(n行上へ) \e[nS
SD スクロールする(n行下へ) \e[nT
REP 直前の文字をn回繰り返す \e[nb
DSR 端末の状態を返す(例:^[[0n = 正常) \e[5n
DSR カーソル位置を返す(例:^[[54;1R = 54行1列) \e[6n
DECTCEM 拡張オプションの設定(カーソルを隠す) \e[?25l
DECTCEM 拡張オプションの設定(カーソルを表示) \e[?25h
SGR テキスト属性の設定 \e[n1;n2;...m

SGR(Select Graphics Rendition)

  • さらに、テキスト属性の設定は、CSIシーケンスのSGRという設定によって行う仕組みである。
  • SGR(\e[m = \x1B5B6D)に複数の引数を ; で区切って与える(\e[n1;n2;...m)ことで、文字色・背景色・明るさ・下線・点滅・反転などを自由に指定できる。


例:

CSI 引数 終了文字SGR 意味
\e[ 1;31;46 m 太字・文字色=赤・背景色=水色に設定する(\e[mなどでリセットするまで効果は続く)
\e[ 4;38;5;150;48;5;200 m 下線・文字色=150・背景色=200に設定する

太字・低輝度・下線・点滅・反転・非表示の引数

番号 意味 番号 意味
0 すべての属性を解除
1 太字 21
2 低輝度 22 太字解除・低輝度解除
3 23
4 下線 24 下線解除
5 点滅 25 点滅解除
6 26
7 反転 27 反転解除
8 非表示 28 非表示解除

色の引数

番号 意味 番号 意味 番号 意味 番号 意味
30 文字色を黒 40 背景色を黒 90 文字色を明るい黒(灰色) 100 背景色を明るい黒(灰色)
31 文字色を赤 41 背景色を赤 91 文字色を明るい赤 101 背景色を明るい赤
32 文字色を緑 42 背景色を緑 92 文字色を明るい緑 102 背景色を明るい緑
33 文字色を黄色 43 背景色を黄色 93 文字色を明るい黄色 103 背景色を明るい黄色
34 文字色を青 44 背景色を青 94 文字色を明るい青 104 背景色を明るい青
35 文字色を赤紫 45 背景色を赤紫 95 文字色を明るい赤紫 105 背景色を明るい赤紫
36 文字色を水色 46 背景色を水色 96 文字色を明るい水色 106 背景色を明るい水色
37 文字色を白 47 背景色を白 97 文字色を明るい白 107 背景色を明るい白
38;5;n 拡張文字色をn(0〜255) 48;5;n 拡張背景色をn(0〜255)
39 文字色を標準 49 背景色を標準

  • 一桁目の数値は、色コードを表現している。
  • RGBを3ビットで表現すると、0〜7の数値に変換できるのだ。

B G R 番号 Color
0 0 0 0 Black
0 0 1 1 Red
0 1 0 2 Green
0 1 1 3 黄色 Yellow
1 0 0 4 Blue
1 0 1 5 赤紫 Magenta
1 1 0 6 水色 Cyan
1 1 1 7 White

体感してみる

以上のエスケープシーケンスを理解して、いよいよ体感してみるのだ!

環境と方法
  • iMac 5K OSX 10.10.2
  • ターミナル.app 2.5.1(343.6)
$ bash --version
GNU bash, バージョン 4.3.33(1)-release (x86_64-apple-darwin14.1.0)
Copyright (C) 2013 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 またはそれ以降 http://gnu.org/licenses/gpl.html
  • ターミナル.app上で、実際にエスケープシーケンスを含む文字列を表示してみるのが一番分かり易いはず。
  • その際、printfコマンドを使った方が、無用な悩みを排除できる。
  • ちなみに、echoコマンドを使う場合は、以下の点に注意する。
    • 通常bashでは、bash内部コマンドのechoが実行される。
    • エスケープシーケンスを書く時は、-eオプションが必要になる。
      • と同時に、-nオプションで行末の改行もキャンセルしたくなることが多かった。
    • それでも、\eをエスケープ制御コードと解釈してくれなかった。(なぜだ?)\033を使う必要がある。
256色を体感する

上記SRGの指定に従えば、ターミナル.appは256色を発色する性能を持っている!

  • すべての色を表示してみた。
for c in {0..255}; do printf "\033[48;5;${c}m%8d\033[m" $c; done; echo

  • 実は、0〜255の色コードは3つに分類できる。

0〜 15の基本色 「黒・赤・緑・黄色・青・赤紫・水色・白」の暗いセットと明るいセットの16色
16〜231の中間色 RGBの中間色6段階の輝度を混ぜ合わせた216色
232〜255のモノクロ 黒から白まで輝度を16段階に区切ったモノクロ16色

for c in {0..7}; do printf "\033[48;5;${c}m%6d\033[m" $c; done; echo; for c in {8..15}; do printf "\033[48;5;${c}m%6d\033[m" $c; done; echo
for c in {16..231}; do printf "\033[48;5;${c}m%6d\033[m" $c; done; echo
for c in {232..255}; do printf "\033[48;5;${c}m%6d\033[m" $c; done; echo

  • さらに中間色について、数値優先の並べ方をやめて、色の変化優先の並べ方にしてみる。
ruby -e '
(0..4).each{|r|
    g=5;(0..5).each{|b|printf("\e[48;5;#{      r*36 +      g*6 + b + 16}m%11d \e[m",       r*36 +      g*6 + b + 16)};
puts};
(0..5).each{|g|
    r=5;(0..5).each{|b|printf("\e[48;5;#{      r*36 + 30 - g*6 + b + 16}m%11d \e[m",       r*36 + 30 - g*6 + b + 16)};
    b=5;(1..5).each{|r|printf("\e[48;5;#{180 - r*36 + 30 - g*6 + b + 16}m%11d \e[m", 180 - r*36 + 30 - g*6 + b + 16)};
puts}'


これで速やかに、好みの色コードを選択できるようになった!

太字・低輝度・下線・点滅・反転・非表示を体感する
  • リセット(\e[m)しなければ、直前のテキスト属性も引き継いで反映される。
for c in 0 30 31 32 33 34 35 36 37
do
  printf "\e[${c}mNormal  \e[${c};1mBold  \e[${c};2mDim  \e[${c};4mUnderLine  \e[${c};5mBlink  \e[${c};7mReverse\e[m  \e[${c};8mHide  \e[m<--Hide\n"
done

  • リセット(\e[m)することで、設定したテキスト属性のみ反映される。
for c in 0 30 31 32 33 34 35 36 37
do
  printf "\e[${c}mNormal  \e[${c};1mBold\e[m  \e[${c};2mDim\e[m  \e[${c};4mUnderLine\e[m  \e[${c};5mBlink\e[m  \e[${c};7mReverse\e[m  \e[${c};8mHide  \e[m<--Hide\n"
done


クルクル回す
  • CSIシーケンスのカーソル移動を利用して、| を回転させてみた。
for i in `seq 1 10`
do
printf "\e[D|" ; sleep 0.2;
printf "\e[D/" ; sleep 0.2;
printf "\e[D-" ; sleep 0.2;
printf "\e[D\\"; sleep 0.2;
done
  • もっとも、| を回転させるだけなら、ASCII制御コードのバックスペースでも実現できるのだけど...。
for i in `seq 1 10`
do
printf "\b|" ; sleep 0.2;
printf "\b/" ; sleep 0.2;
printf "\b-" ; sleep 0.2;
printf "\b\\"; sleep 0.2;
done
プログレスバーを作る
  • でもプログレスバーを作ろうとすると、バックスペースだけではちょっとキツイ...。
  • カーソル位置の保存と復元、繰り返しのエスケープシーケンスを利用して作ってみる。
printf "\e7|-\e[-98b|"; for i in {0..100}; do printf "\e8|\e[${i}b"; sleep 0.05; done; echo
||||||||||||||||||||||||||||||||||||||||||----------------------------------------------------------|
端末リセット
printf "\ec"
    • いちばん下までスクロールして画面に表示されているテキストのみクリアされた。
    • 画面に表示されていないスクロール領域のテキストは残った。
    • カーソルは画面左上に移動された。
    • タブは8文字ごとにリセットされた。
縦倍・横倍表示
printf "\e#3ABCDEFG\n"
printf "\e#4ABCDEFG\n"
printf "\e#3ABCDEFG\n"; printf "\e#4ABCDEFG\n"
printf "\e#5ABCDEFG\n"
printf "\e#6ABCDEFG\n"

    • 縦横倍にする時は、上側と下側に分けて2行で表示する仕組みのようだ。
Eで埋める
printf "\e#8"

    • 何じゃこれは?各行のフォーマットの状態でも調べるためか?

参考ページ

  • 以下のページがたいへん参考になりました。感謝です!

ISO 2022について


ANSIエスケープシーケンス


VT-100


xterm


エスケープシーケンスの詳細


Linux console_codesについて