【C言語】printfで色をつける。SGRによる画面出力制御について。

TL;DR

printfで赤色をつけたい場合は以下のように記述します。

printf("\x1b[031mHello, World!\x1b[0m\n");

はじめに

C言語でコーディングしているとき、標準出力に色をつけたいことが多々あると思います。私自身も大学の課題やシェルの実装をしているときなどにそういう気持ちになりました。

今回はC言語のprintfで色をつける方法、さらに掘り下げてSGR1コマンドについて調べた結果を残しておこうと思います。

printfで文字・背景色を変える

C言語のprintfで文字・背景色を変えるには、\x1b[ または \033[ を記述したのち、<Pn>m で制御を行います。具体的には以下の通りです。

printf("\x1b[031m");        // 文字色を赤色にする
printf("Hello, World!\n");  // 赤色の'Hello, World!'が出力される
printf("\x1b[0m");          // 設定をリセットする

後述しますが、最後の \x1b[0m を記述することで設定した属性をすべて解除することができます。この記法による動作はOSに組み込まれているAPIを利用するものであるため、プログラム中で設定した属性はこの別のプログラムにも影響を与えます。そのため、設定した属性はプログラム内で必ず解除するようにした方がいいと思います。また、設定した属性を個別で解除することもできます。詳細は、対応している制御コード一覧のSGR部分を参照するといいです。その他にも、以下のような<Pn>で制御ができます。

文字装飾・文字色の制御

<Pn>動作
0文字属性をすべて解除
1高輝度/太字に設定
2低輝度に設定
3イタリックに設定
4下線をつける
5低速点滅
6高速点滅
7文字色と背景色を反転
8文字色を背景色と同じにする
9打ち消し線をつける
212重下線をつける
30文字色を黒に設定
31文字色を赤に設定
32文字色を緑に設定
33文字色を黄色に設定
34文字色を青に設定
35文字色をマゼンタに設定
36文字色をシアンに設定
37文字色を白に設定
38;2;r;g;b文字色をR:r, G:g, B:b で指定する
38;5;Ps文字色を0-255の範囲で用意された色番号 Ps に設定する
39文字色を標準にする
90-97文字色を30-37に対応する高輝度色に設定する

背景色の制御

<Pn>動作
40背景色を黒に設定
41背景色を赤に設定
42背景色を緑に設定
43背景色を黄色に設定
44背景色を青に設定
45背景色をマゼンタに設定
46背景色をシアンに設定
47背景色を白に設定
48;2;r;g;b背景色をR:r, G:g, B:b で指定する
48;5;Ps背景色を0-255の範囲で用意された色番号 Ps に設定する
100-107背景色を40-47に対応する高輝度色に設定する

動作一覧

私の環境2では以下のように表示されました。006の高速点滅はあまり対応されてないそうです。また、090以降の高輝度色の設定も上手く設定されませんでした。

#include <stdio.h>

#define ESC 0x1b
#define CSI 0x5b

int main(void)
{
	int sgr[110];
	int flag = 0;
	int index = 0;
	for (int i = 0; i <= 9; i++) sgr[index++] = i;
	sgr[index++] = 21;
	for (int i = 30; i <= 48; i++) sgr[index++] = i;
	for (int i = 90; i <= 97; i++) sgr[index++] = i;
	for (int i = 100; i <= 107; i++) sgr[index++] = i;
	index = 0;
	for (int i = 0; i < 11; i++){
		int flag = 0;
		for (int j = 0; j < 10; j++){
			int Pn = i * 10 + j;
			if (Pn != sgr[index]) continue;
			if (flag == 0){
				flag = 1;
				printf("%3d- : ", i * 10);
			}
			index++;
			printf("%c%c%d%c", ESC, CSI, Pn, 'm');
			printf("%03d", Pn);
			printf("%c%c%d%c ", ESC, CSI, 0, 'm');
		}
		if (flag) printf("\n");
	}

	return 0;
}

このプログラムは少し変な書き方をしていますが、普通に 3. printfで文字・背景色を変える のように書くか、以下のようにひとまとめにして書いても大丈夫です。

printf("\x1b[031mHello, World!\x1b[0m\n");

ANSIエスケープシーケンス

エスケープシーケンスという言葉をよく耳にすると思います。文字しか入力できない環境下で、画面制御や表示できない文字を入力するために生み出されたものがエスケープシーケンスです。printfで改行をするための \n や、ヌル文字として使われる \0 もエスケープシーケンスになります。

printfで色を変える際、\x1b\033 という文字を入力しました。これは「エスケープ処理を開始しますよ」という意味のコードで、ANSIエスケープコード、またはANSIエスケープシーケンスと呼ばれ、ESCと略されることがほとんどです。ESCに対応するASCII文字コードが16進数で1B, 8進数で33になるため、これを記述しているのがC言語です。Shell ScriptでもANSIエスケープシーケンスを利用することができ、\e でエスケープ処理を始めることができるようです。

ANSIエスケープシーケンスを用いれば、カーソル移動、カーソル点滅、画面クリアなど様々な動作を行うことができます。詳しくは、コンソールの仮想ターミナル シーケンス等を参照されるといいと思います。この1つにCSI3シーケンスが存在し、これを使うことでSGRコマンドを呼び出し、コンソールに出力する文字に色をつけることができるようになります。

ESC(0x1B)に続けて左角括弧の「[」(0x5B)を入力することでCSIシーケンス処理を開始します。動作一覧で記述したソースコードのdefineはこれを指定したものでした。CSIシーケンスにはSGRコマンドと呼ばれるものがあり、コンソール出力文字のグラフィック設定を行うことができます。ANSIエスケープシーケンスで以下のように記述することでSGRが呼ばれます。

ESC[<Pn>m
// ESC(エスケープコード、エスケープ処理開始) + [(CSIシーケンス開始) + <Pn>(パターン値) + m

 SGRコマンドというよりは、「上記のANSIエスケープシーケンスの記法をSGRと略称する」と捉えた方が正しいかもしれません。

参考文献・サイト

 

  1. Select Graphic Rendition
  2. Windows 11 Pro + WSL2 + Ubuntu 20.04 LTS
  3. Control Sequence Introducer