jqとls --colorの不思議とisatty

コマンド出力に色が着く

ls --colorなどを実行すると文字に色が着く。こういうふうに。

f:id:podhmo:20180908224043p:plain

これはjqも同じで普通にコマンドを実行すると色が着く。 f:id:podhmo:20180908224108p:plain

python

これらはansi-color-code付きで出力されているので色が着く。pythonでやってみるとこういう感じ。

print("[01;34mhello[0m world")

f:id:podhmo:20180908224206p:plain

リダイレクトしたりパイプでつなげると色がなくなる。

ls --colorはリダイレクトしたりパイプでつなげても色が残るのに対して、jqはリダイレクトすると色が消える。例えばjqの出力をcatに渡してみる。色が消える。

f:id:podhmo:20180908224232p:plain

この振る舞いはどうやっているんだろう?というのが今回の話。

isatty

正体はisattyでこれは渡されたファイルディスクリプタがターミナルから開かれているかどうか調べてくれるもの。実際jqなどのコードを覗いてみたら使われている箇所があった。

おもむろにmanを見てみるとそれっぽいことが書かれている。

NAME
       isatty - test whether a file descriptor refers to a terminal

SYNOPSIS
       #include <unistd.h>

       int isatty(int fd);

DESCRIPTION
       The isatty() function tests whether fd is an open file descriptor referring to a terminal.

RETURN VALUE
       isatty() returns 1 if fd is an open file descriptor referring to a terminal; otherwise 0 is returned, and errno is set to indicate the error.

pythonでもisatty

pythonにもisattyは存在していて、os.isatty()を使っても良いけれど。

ファイル記述子 fd がオープンされていて、 tty (のような) デバイスに接続されている場合、 True を返します。そうでない場合は False を返します。

標準出力などがisatty()メソッドを持っているのでこれを使えば良い。

実際に使って確かめてみる。

$ python -c 'import sys; print(sys.stdout.isatty())'
True
$ python -c 'import sys; print(sys.stdout.isatty())' | cat
False

良さそう。

pythonでもjqの振る舞いを模倣する

あとはpythonでもjqの振る舞いを模倣するにはどうするかという話。isattyがTrueだったら色を付けて出力すれば良い。

import sys

if sys.stdout.isatty():
    COLOR_ON = "[01;34m"
    COLOR_OFF = "[01;34m"
else:
    COLOR_ON = ""
    COLOR_OFF = ""

print(f"{COLOR_ON}hello{COLOR_OFF} world")

今度は色が付いたりつかなかったりしてくれる。

f:id:podhmo:20180908224315p:plain