目次
- ANSIエスケープシーケンスとは
- ANSIエスケープシーケンスの制御コードは邪魔になることも…
- 制御コードを抑制する為の条件は…?
- STDOUT(標準出力)の出力先によって切り替える
- 端末エミュレーターが対応していない場合に備える
- 【まとめ】文字修飾を施すべきか抑制するべきかを判定する関数
- Appendix
ANSIエスケープシーケンスとは
コンソールや端末エミュレーターには、文字に色や太字などの修飾を施せる機能があります。
これはANSIエスケープシーケンスという制御コードを埋め込む事で実現できます。
使い方については、ググると判り易いページが沢山見つかると思います。⇒「ANSIエスケープシーケンスとは」
ANSIエスケープシーケンスの使用例
[bash]
$ echo '文字を^[[31m赤く^[[0mしたり^[[4m下線を引いたり^[[0mすると読みやすい。'
文字を赤くしたり下線を引いたりすると読みやすい。
$
ANSIエスケープシーケンスの制御コードは邪魔になることも…
文字を修飾して読みやすくするANSIエスケープシーケンスですが、この制御コードは、コマンドをパイプで繋いだり、リダイレクトする時には、邪魔になることが多いです。
例えば、「赤くしたり」でgrepをかけても、「く」と「し」の間に制御コードが入っているのでマッチしません。これは感覚的に許容できない回避させたい挙動です。
[bash]
$ ## 制御コードが邪魔でgrepで思うようにマッチできない
$ echo '文字を^[[31m赤く^[[0mしたり^[[4m下線を引いたり^[[0mすると読みやすい。' | grep '赤くしたり'
$
制御コードを抑制する為の条件は…?
このような状況を回避する常套手段を知りたかったので、lsコマンドのソースを確認してみました。すると、凄くシンプルな方法が使われていました。
[ls.c]
if (isatty(STDOUT_FILENO)) {
なんと出力先がTTYか否かを確認する関数が用意されていたのです。
この、isatty()はposixの関数で、ヘッダーファイルは<unistd.h>なので、Linuxなら問題なく使えそうです。Windowsにも移植されていて、_isatty()という名前で呼び出せるみたいです。_isatty()関数のヘッダーファイルは<io.h>です。
isatty()関数のドキュメント
STDOUT(標準出力)の出力先によって切り替える
C言語で、「出力先がTTYか否か?」という関数を書いてみました。「_WIN32」マクロは、Visual Cコンパイラで定義されています。「_WIN32」マクロが定義されているか否かで、コンパイラ別のコードを振り分けています。
#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
int is_stdout_a_tty( void )
{
#ifdef _WIN32
return _isatty( _fileno( stdout ) );
#else
return isatty( STDOUT_FILENO );
#endif
}
端末エミュレーターが対応していない場合に備える
Windows10に付属しているコマンドプロンプトは、デフォルトだとANSIエスケープシーケンスに対応していません。
これは、GetConsoleMode()というAPIで確認する事ができます。また、SetConsoleMode()というAPIを使えば、ANSIエスケープシーケンスを使えるようにできるみたいです。今回はこの対応はしていません。シンプルに「ANSIエスケープシーケンスに対応しているか否か?」を確認するだけの関数にしてみました。
#ifdef _WIN32
#include <windows.h>
#endif
int does_it_support_ansi_escape_sequences( void )
{
#ifdef _WIN32
int fh;
DWORD dwMode;fh = _fileno( stdout );
HANDLE hCon = ( HANDLE )_get_osfhandle( fh );
if( ! GetConsoleMode( hCon, &dwMode ) ){
return 0;
}
return ( dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING );
#else
return 1;
#endif
}
【まとめ】文字修飾を施すべきか抑制するべきかを判定する関数
これまでに作成した関数を組み合わせて、ANSIエスケープシーケンスの使用有無を判定する関数にまとめます。
[is_stdout_a_tty.c]
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#else
#include <unistd.h>
#endif
#define MSG_COLOR_SUPPORT "Text in \033[31mred\033[0m or \033[4munderlined\033[0m makes it easier to read."
#define MSG_NON_COLOR "Text in red or underlined makes it easier to read."
int is_stdout_a_tty( void )
{
#ifdef _WIN32
return _isatty( _fileno( stdout ) );
#else
return isatty( STDOUT_FILENO );
#endif
}
int does_it_support_ansi_escape_sequences( void )
{
#ifdef _WIN32
int fh;
DWORD dwMode;fh = _fileno( stdout );
HANDLE hCon = ( HANDLE )_get_osfhandle( fh );
if( ! GetConsoleMode( hCon, &dwMode ) ){
return 0;
}
return ( dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING );
#else
return 1;
#endif
}
int use_color( void )
{
return is_stdout_a_tty() &&
does_it_support_ansi_escape_sequences();
}
int main( void )
{
if( use_color() ){
fprintf( stderr, "yes\n" );
printf( MSG_COLOR_SUPPORT );
}else{
fprintf( stderr, "no\n" );
printf( MSG_NON_COLOR );
}return 0;
}
動作確認
Cygwin 3.3.6-1, gcc (GCC) 11.3.0
$ gcc -o is_stdout_a_tty_cygwin is_stdout_a_tty.c
$ ./is_stdout_a_tty_cygwin
yes
Text in red or underlined makes it easier to read.$ ./is_stdout_a_tty_cygwin | grep 'red or'
no
Text in red or underlined makes it easier to read.$
Windows 10, Visual Studio 2022, x64 Native Tools Command Prompt
>cl /nologo /Fe:is_stdout_a_tty_win64.exe is_stdout_a_tty.c
is_stdout_a_tty.c>is_stdout_a_tty_win64.exe
no
Text in red or underlined makes it easier to read.>is_stdout_a_tty_win64.exe | grep 'red or'
no
Text in red or underlined makes it easier to read.>
Appendix
C言語以外の環境における対応