エスケープシーケンス

作成:

コンソール制御の話をしていこうと思う。 ここでは必要となる基本的な制御コードと、 その記述に必要なエスケープシーケンスについて説明する。

制御コード

まず制御コードについて簡単に説明する。 現在一般的に使用されている、半角英数字を表現するのに使われている1byteコードは、 ASCII(American Standard Code for Information Interchange)コードである。 ASCIIコードは1byte=8bitのうち、7bitを利用し、各番号に文字が割り当てていて、その羅列で文字列を表現する。 しかし、英語圏で使われる、英数字、記号は合わせて割り当てても、7bit=128パターンにあまりがある。 その余った領域に表示文字以外の意味が割り当てられている。これを制御コードと呼んでいる。

制御コードと言われると、何やら難しい物を想像するかもしれないが、何も特別なものではない。 例えば「改行」や、「タブ」も制御コードの一つだ。 プログラマにかぎらずだれでも利用しているものだ。 当然、中にはプログラマぐらいしか知らなくていいようなものや、 現代ではほぼ使われなくなってしまったものもある。

ASCIIコードに含まれる制御コードは、例えば ASCIIコード表 などで確認できる。 ひと通りどんなものがあるのかぐらいは見ておいても損はないだろう。

誤解を恐れずに言えば、制御コードも文字の一種であり、 プログラミングでのその扱いも文字と同じで良い。 ただし、表示文字ではないので、他の文字と同じようにはソースコード上で記述できない。

1byte文字=ASCIIコードってのは厳密には間違い、 というか正確ではないのだが、ここは制御コードの話なのでご容赦を。

制御コードの記述とエスケープシーケンス

次に、エスケープシーケンスについて説明する。 C言語では文字列リテラル内に、文字として記述できない、キーボードでの入力が難しい、 もしくは、他の意味が付与されているために記述するのが難しい、文字コードを記述するために、 エスケープシーケンスという仕組みが用意されている。

これも何も特別なものではなく、C言語の勉強を始めたばかりの人であっても、見たことはあるはずである。 なぜなら、一番最初に見ることになるであろう「Hello World」で、既に登場しているからである。

#include <stdio.h>

main() {
  printf("hello world!\n");
}

\nがそれである。 \nはいわゆる改行コードとして機能する。

制御コードは表示文字ではない。 C言語のソースコードの中に記述してしまうと、当然通常のテキストエディタ等では文字として見ることができないし、 キーボードでは通常入力できないものもある。 入力できたとしてもソースコードというテキストの制御コードなのか、 その中に記述された文字列リテラル内のコードなのかの区別がつかない。 上記の例だと、記述上は"の手前に改行コードを入れれば直接記述できそうな気もするが、それは文法違反である。 そんな風に、文字列リテラル内にはそのまま記述することはできない(難しい)のだ。

そこでエスケープシーケンスが利用できる。 エスケーシーケンスは\から始まる概ね2文字の文字の組で構成されていて、 このシーケンスはコンパイラによって解釈され、各定義の文字コードに置換される。

以下に、C言語で用いられるエスケープシーケンスをまとめる。

記述意味
\a ビープ音や効果音を鳴らす
\b バックスペース、直前の文字を消す
\f From Feed、改ページ。コンソールでは横方向の位置そのままで次行に移動する場合が多い
\n LF ラインフィード、次の行の先頭へ移動。改行コードとして使われる場合もある。
\r CR キャリッジリターン、行の先頭に戻る。改行コードとして使われる場合もある。
\t 水平タブ
\v 垂直タブ。コンソール上では横方向の位置そのままで次の行に移動する場合が多い
\0 ヌル文字。文字コード0であり、C言語では文字列の終端判定に使用される。 文字列リテラルの中に記述すると、 その後ろに文字列を続けても、このコードで文字列の終了と判定されてしまう。 また、その文字列を出力しても、文字列として扱っている場合は、この文字コードは出力されない。

これらの制御コードは、「概ね、このように処理される。」とはいえても、 それを解釈するコンソールやテキストエディタ等によって結果が異なる場合がある。 例えば、テキストエディタではビープ音はならないし(なるものも中にはあるだろうが)、 コンソールであってもビープ音が無効になっている場合もある。 多くのテキストエディタは、複数の形式の改行コードを適切に処理してくれるが、 Windows付属のメモ帳では、長い間Windowsの改行コードであるCR+LFでなければ適切に処理してくれなかった。

システムによって、どのASCIIコードをテキストの改行として扱うかは異なる。 それぞれの改行コードと、それを採用しているシステムの対応は、以下のようになっている。

LF
UNIX及びUNIX系システム、Linux、Mac OS X、など
CR+LF
OS/2、MS-DOS、Windows
CR
Mac OS(9まで)

現在、主に利用されているシステムでは、LFもしくはCR+LFであり、Windowsを除けば、LFである。 シェアという観点ではWindowsの存在は無視できない。 パソコンカテゴリでは一番多いぐらいだろうが、種別という観点ではWindowsとそれ以外という関係になっている。 そのため、かどうかは分からないが、複数のシステム間でやりとりするデータについては、LFで統一される場合が多い。 このサイトのHTMLデータもWindows上で制作しているが、改行コードはLFにしている。 プログラムコードの改行コードをLFで統一しているコーディング規約もよく見かける。

あくまで標準の改行コードはこれ、というだけであって、 複数のシステムのデータを扱うことになるWeb系のツールや開発ツールなどは特にであるが、 アプリケーションレベルで対応されていて、どちらの改行コードでも問題なく扱える場合が多い。 開発者はもちろん意識する必要があるが、ユーザは特に意識しないですむ場合が多いだろう。

これらの出力を行うサンプルコードを出しても、あまりおもしろいものはない。 この後の記事であるが、コンソールによるプログレス表示で、CRの出力を使ったテクニックを紹介している。 これが、制御コードの例として面白いかもしれない。 CRは改行コードとして扱われる場合があるが、多くのコンソールでは、 カーソルを次行ではなく、現在の行の先頭に戻すように機能する。 カーソルを先頭に戻した後に、その行を上書きするように別の出力を行うことで、 そこが変化していくように見せることができるのである。

改めて簡単なサンプルコード

#include <stdio.h>
#include <unistd.h>

int main(int argc, char**argv) {
  int i;
  for (i = 0; i <= 100; i++) {
    fprintf(stderr, "\r[%3d / 100]", i);
    usleep(100000);
  }
}

そして、実行結果を如何に示す。 アニメーションなのでJavaScriptを用いて表現している。 JavaScriptが無効になっているとわけがわからないと思う。


任意文字コードを記述するエスケープシーケンス

任意のコードを表現するために以下のようなエスケープシーケンスも用意されている。 任意コードであるため、わざわざこのような方法を使わなくても記述できる文字コードも同じ方式で埋め込むことは可能である。

記述意味
\xXX (XXには16進数を記述)16進数で記述された1byteコードに変換される
\ooo (oooには8進数を記述)8進数で記述された1byteコードに変換される。
\uxxxx (xxxxには16進数を記述)16進数(2byte)で表現されたユニバーサルキャラクタ名(Unicode)に変換される。 C99以降で有効
\Uxxxxxxxx (xxxxxxxxには16進数を記述)16進数(4byte)で表現されたユニバーサルキャラクタ名(Unicode)に変換される。 C99以降で有効

上記のうち、C99以降で有効と書いたUnicodeの記述については、 gcc4.x以下では、そのままでは利用できない。 gcc4.x以下はC89にGNU拡張を加えた仕様が標準であるためである。 -std=c99もしくは-std=gnu99オプションを加えて、C99仕様でコンパイルを行う必要がある。

Unicodeについては、ソースコードの文字コードをUnicodeにすれば、直接記述できる場合が多く、 直接記述できる場合は利用しないことが推奨されている。

特別な意味を無効化するエスケープ

コンソール制御から外れるが、ついでなので解説する。 前記のような特別な意味が与えられたエスケープシーケンス以外に、 文字の特別な機能を打ち消すエスケープシーケンスがある。

例えば、文字列リテラルの区切りとして"(ダブルクォーテーション)が用いられるが、 これを文字列リテラル内に記述してしまうと、そこが文字列リテラルの終了と判断されてしまい、うまく記述できない。 文字リテラルの区切り文字'(シングルクォーテーション)も同様である。 そこで、\"と記述することで、文字列の区切り文字としての機能が失われ、 ただの文字"として、文字列リテラル内に記述することができる。 同様にエスケープシーケンスの開始文字としての機能が\に与えられているため、 \という文字そのものを文字列リテラル内に記述できなくない。 そこで、\\と2つ続けて書くことで、 一つ目の\が2つ目の\のエスケープシーケンスの開始文字としての機能を無効にし、 ただの文字\として、文字列リテラル内に記述することができるようになる。

つまり、以下のように書くことで

printf("\"\'\\\n");

以下の様な出力を得ることができる。

"'\

このように、\を使って、文字の特別な意味を無効化することを「エスケープする」と表現したりする。

このエスケープは、改行コードのような制御コードの意味も打ち消すことができる。

以下の様な定義では、ちょっと一行で書くには長くて可読性が悪いので改行を入れたいと思うだろう。 しかし、単純に途中に改行を入れてしまうとそこで定義の終了と判断されてしまい、コンパイルエラーになる。

#define assert(__e) ((__e) ? (void)0 : __assert_func (__FILE__, __LINE__, __ASSERT_FUNC, #__e))

そこで、改行コードをエスケープして、以下のように記述することが可能だ。

#define assert(__e) ((__e) ? (void)0 : \
      __assert_func (__FILE__, __LINE__, __ASSERT_FUNC, #__e))

改行コードをエスケープするので、この表示ではわからないだろうが、 \と改行コードの間にスペースなどが入ってはいけない。 gccではエラーではなく警告になるようだが、気をつけよう。