ターミナルを操作してると、ものによっては色が付くけど、コレがどうやって実現されてるかを調べた。
https://en.wikipedia.org/wiki/ANSI_escape_code で制御文字が定義されていて(ASCII の 0x1B)、ターミナルがこの制御文字を読み込んで色を付けているっぽい。なので利用しているターミナル(Teraterm とか Windows PowerShell とか)に依存した技術っぽい。
たとえば、PowerShell から Python で print('\N{ESC}[31;4mhello\u001b[mworld')
を実行しても赤線+下線が付けられるし、
Windows PowerShell から ssh した Bash で printf "\u1b[31;4mhello\033[mworld\n"
を実行しても赤色+下線が付けられる。
うーん、でも ls -al
で出力されるテキストを less で読んでも普通のテキストにしか見えないんだけど…
$ ls -al /tmp/ | less
total 0
drwxrwxrwt. 11 root root 220 Aug 5 13:31 .
dr-xr-xr-x. 18 root root 237 Jul 25 22:30 ..
drwxrwxrwt. 2 root root 40 Aug 5 12:54 .ICE-unix
drwxrwxrwt. 2 root root 40 Aug 5 12:54 .X11-unix
drwxrwxrwt. 2 root root 40 Aug 5 12:54 .XIM-unix
drwxrwxrwt. 2 root root 40 Aug 5 12:54 .font-unix
drwx------. 3 root root 60 Aug 5 12:54 systemd-private-42b4409a4786411fad91dc5d123c51cb-chronyd.service-gzz3N3
drwx------. 3 root root 60 Aug 5 12:54 systemd-private-42b4409a4786411fad91dc5d123c51cb-dbus-broker.service-Y0wOSE
drwx------. 3 root root 60 Aug 5 12:54 systemd-private-42b4409a4786411fad91dc5d123c51cb-policy-routes@enX0.service-RfRofl
drwx------. 3 root root 60 Aug 5 12:54 systemd-private-42b4409a4786411fad91dc5d123c51cb-systemd-logind.service-QoGcYH
drwx------. 3 root root 60 Aug 5 12:54 systemd-private-42b4409a4786411fad91dc5d123c51cb-systemd-resolved.service-UE8UVV
と思ったけど、これは ls コマンドのデフォルトの挙動が、標準出力がターミナルでなければ色を消す設定になっているからだった。 --color=always
を設定すれば、色の制御コードを常に出力してくれる。
—color[=WHEN]
colorize the output; WHEN can be ‘always’ (default if omitted), ‘auto’, or ‘never’; more info below
… Using color to distinguish file types is disabled both by default and with —color=never. With —color=auto, ls emits color codes only when
standard output is connected to a terminal. The LS_COLORS environment variable can change the settings. Use the dircolors command to set it.
ls の man から引用
たしかに --color=always
を使うと制御コードが表示される。
ls -al /tmp --color=always | less
total 0
drwxrwxrwt. 11 root root 220 Aug 5 13:33 ESC[0mESC[30;42m.ESC[0m
dr-xr-xr-x. 18 root root 237 Jul 25 22:30 ESC[01;34m..ESC[0m
drwxrwxrwt. 2 root root 40 Aug 5 12:54 ESC[30;42m.ICE-unixESC[0m
drwxrwxrwt. 2 root root 40 Aug 5 12:54 ESC[30;42m.X11-unixESC[0m
drwxrwxrwt. 2 root root 40 Aug 5 12:54 ESC[30;42m.XIM-unixESC[0m
drwxrwxrwt. 2 root root 40 Aug 5 12:54 ESC[30;42m.font-unixESC[0m
drwx------. 3 root root 60 Aug 5 12:54 ESC[01;34msystemd-private-42b4409a4786411fad91dc5d123c51cb-chronyd.service-gzz3N3ESC[0mESC[K
drwx------. 3 root root 60 Aug 5 12:54 ESC[01;34msystemd-private-42b4409a4786411fad91dc5d123c51cb-dbus-broker.service-Y0wOSEESC[0mESC[K
drwx------. 3 root root 60 Aug 5 12:54 ESC[01;34msystemd-private-42b4409a4786411fad91dc5d123c51cb-policy-routes@enX0.service-RfRoflESC[0mESC[K
drwx------. 3 root root 60 Aug 5 12:54 ESC[01;34msystemd-private-42b4409a4786411fad91dc5d123c51cb-systemd-logind.service-QoGcYHESC[0mESC[K
drwx------. 3 root root 60 Aug 5 12:54 ESC[01;34msystemd-private-42b4409a4786411fad91dc5d123c51cb-systemd-resolved.service-UE8UVVESC[0mESC[K
なお、less はデフォルトだと制御コードをエスケープするが、 -R オプションをつけると色が付けられる。
ls -al /tmp --color=always | less -R
grep とかにも ls と同様のオプションがあるようなので、これで grep の一致した部分の色を消さずに less できますね。やったぜ。
grep -r "Amazon Linux" --color=always | less -R
複数 grep をかけたとき(grep A | grep B
で A と B に色付ける的な)も色を保てるっぽい。
ただ --color=always
を付けたときの注意点としては、目には見えないけど制御文字は付いてるので、思ってる文字列ではない。 とくにパイプの途中で grep してるとコーナーケースではつまずきそうなのと、テキストに出力するなら邪魔でしかないので、使うかというと微妙そう。
// Amazon Linux があるが
$ grep -r "Amazon" /etc/os-release --color=always
NAME="Amazon Linux"
// --color=always をつけると Amazon の前後に見えない制御文字があるので Amazon Linux に一致しなくなる
PRETTY_NAME="Amazon Linux 2023"
$ grep -r "Amazon" /etc/os-release --color=always | grep "Amazon Linux"
参考
ところで ls コマンドは標準出力がターミナルでなければ色を消すといってるが、そんなの判別する方法あるんだっけ…と思って coreutils のコード見たところ(upstream だけど)、isatty(3) という API があるらしい。
ためしにサンプルコード作ってみたが、まあそうなんだ、というかんじ。
$ cat main.c
#include <unistd.h>
#include <stdio.h>
void main() {
if (isatty(1)) {
printf("stdout is a tty\n");
} else {
printf("stdout is not a tty\n");
}
}
$ gcc main.c
$ ./a.out
stdout is a tty
$ ./a.out | cat
stdout is not a tty