[unix] 標準出力の接続先によって、動作を変える

2017年5月22日月曜日

C linux unix

[unix] パイプ処理時のバッファリングではgrepやsedに出力のバッファリングオプションがオプションがあるのを見た。
他にも出力先によって、動作が変わるコマンドはある。

lsの出力

例えば、lsも出力先が端末だと、ファイル名を適当に区切って並べてくれるけど、出力先がパイプだと、ファイル名を縦に並べる。
むしろ、縦に並べる方が本来で、端末の場合は見やすくしてくれていると考えた方がいいかもしれない。

$ ls
bar  buzz foo
$ ls | cat
bar
buzz
foo
$

標準出力の接続先を確認

こんな風に標準出力の接続先に応じて、プログラムの動作を変えたい時はisattyやfstatを使って、ストリームがどこに繋がっているか調べればいい。

out_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    struct stat st;

    if (isatty(fileno(stdout))) {
        printf("terminal.\n");
        return 0;
    }

    fstat(fileno(stdout), &st);
    if (S_ISFIFO(st.st_mode)) {
        printf("pipe.\n");
        return 0;
    }

    printf("file.\n");
    return 0;
}
$ ./out_test
terminal.
$ ./out_test | cat
pipe.
$ ./out_test >test.log; cat test.log
file.
$ 

Linux Programmer's Manual ISATTY(3)
Linux Programmer's Manual STAT(2)

バッファリングモードを変更

grepやsedのようにオプションとかで出力のバッファリングモードを変更したい場合はsetvbufで変更できる。
例えば、出力先がパイプの場合でも、1行毎に出力したい場合は _IOLBF を指定する。

buffer_test.c
#include <stdio.h>

int main(void) {
    char buf[4096];

    setvbuf(stdout, NULL, _IOLBF, 0);

    while (fgets(buf, 4096, stdin)) {
        fputs(buf, stdout);
    }

    return 0;
}

これをURL同様に実行すると、パイプを経由しても、1行毎に出力されることがわかる。
$ seq 10 | xargs -I% sh -c 'sleep 1; echo %' | ./buffer_test | cat
1
2
3
4
5
6
7
8
9
10
$

Linux Programmer's Manual SETBUF(3)