手抜き記事です.
というかこっちはサブブログなので平常運転がこれでないといかんのですが.
というかこっちはサブブログなので平常運転がこれでないといかんのですが.
さて,コマンドラインプログラムにおいて,標準入力・標準出力・標準エラー出力がそれぞれ端末かリダイレクトまたはパイプされて別のファイルまたはプロセスに渡されているかどうかを調べたいことがあります.
今日はその方法を.
そもそもなんでそんなことしたいか
POSIXのエスケープシーケンスによって端末に出力する文字の色を適当に変えてみましょう.
#include <iostream> #include <string> #include <boost/format.hpp> std::string gen(int col) { static const char esc = 0x1b; return (boost::format("%c[%dm") % esc % col).str(); } int main() { std::cout << gen(31); std::cout << "hoge" << std::endl; }
エスケープシーケンス自体は本質的じゃないので説明は他に譲るとして.
このプログラムを実行すると,赤文字でhogeと出力されます.∩( ・ω・)∩やったね
でもこうすると…
% ./a.out > out.txt % less out.txt
lessで表示される文字列はなにやらヘンです.
そう,リダイレクト先ファイルにエスケープシーケンスまで出力されてしまうからです.
ちなみにlessじゃなくcatで出すと,色がちゃんとついて表示されます.何も考えてなかったら,全くもって期待通りだとスルーしてしまいますね.
で,これでは困るので,実行時に標準入出力先が端末かそれ以外か調べてそれに応じて出力文字列を変えましょう.
これは,例えば標準コマンドのlsなんかで普通に使われています.
ということでやり方
細かく説明する必要はまっっったく無いです.システムコールで一発です.
#include <iostream> #include <unistd.h> int main() { const bool stdin_console = isatty(STDIN_FILENO); const bool stdout_console = isatty(STDOUT_FILENO); const bool stderr_console = isatty(STDERR_FILENO); std::cout << "stdin->" << (stdin_console ? "" : " not") << " terminal" << std::endl; std::cout << "stdout->" << (stdout_console ? "" : " not") << " terminal" << std::endl; std::cout << "stderr->" << (stderr_console ? "" : " not") << " terminal" << std::endl; }
はい,これだけです.
そんなかんじでさっきのプログラムを書き換えるなら,
#include <iostream> #include <string> #include <unistd.h> #include <boost/format.hpp> #include <boost/optional.hpp> std::string gen(int col) { static const char esc = 0x1b; static boost::optional<bool> is_atty; if(!is_atty) is_atty = isatty(STDOUT_FILENO); if(!*is_atty) return std::string(); return (boost::format("%c[%dm") % esc % col).str(); } int main() { std::cout << gen(31); std::cout << "hoge" << std::endl; }
システムコールですし毎回呼ぶのはアレなのでis_attyにisattyした結果を入れときます.
最初だけはちゃんと判定するようにoptionalに入れてます.
この方法で,端末の時とそうでない時で自由に出力をいじれます.
そうは言っても,出力の形式をいじることはあっても出力の内容自体をいじることはあまり賢明ではないと思います.
まさにlsコマンドでの使い方がぴったりでしょう.
#てか上のプログラムってたぶんスレッドセーフじゃないですね…