tomyamaのブログ

日記・雑記。

ANSIエスケープシーケンスを使えない環境に対応する


目次


ANSIエスケープシーケンスとは

 

コンソールや端末エミュレーターには、文字に色や太字などの修飾を施せる機能があります。

これは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言語以外の環境における対応