読者です 読者をやめる 読者になる 読者になる

標準入力が pipeかどうかの判定

c

the silver searcherは stdinが ttyに関連付けられていないと
標準入力が pipeと判定しているんですが、そのせいで単純に
エディタから agコマンドを叩くと標準入力待ちになってしまいます。


問題となる部分は src/options.cの以下の部分です

    /* stdin isn't a tty. something's probably being piped to ag */
    if (!isatty(fileno(stdin))) {
        opts.search_stream = 1;
    }


一応確かめてみます。

#include <stdio.h>
#include <unistd.h>

int main (void)
{
    if (isatty(STDIN_FILENO)) {
        printf("stdin is tty\n");
    } else {
        printf("stdin is not tty\n");
    }

    return 0;
}

ターミナルエミュレータで実行すると以下のような結果が得られます。

% ./isatty.out
stdin is tty
% echo hoge | ./isatty.out
stdin is not tty

しかし Emacsからコマンドを叩いて見ると以下のような結果になります。

(call-process-shell-command "./isatty.out" nil t) ;; => stdin is not tty
(call-process-shell-command "echo hoge | ./isatty.out" nil t) ;; => stdin is not tty

ttyに関連づいていないので, 両方 elseの方に入ってしまい、結果が異なります
まあ当たり前の挙動ではあるんですが、the silver searcherが期待するものとは異なります。


もっとまともな判定方法がないのかと思って調べてみました。

解決方法

S_ISFIFOで標準入力を調べれば、ttyに関連づいているか関係なく
pipeかどうかを判定できるようです。(Perlだと -p FILEで OK)

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

int main (void)
{
    struct stat st;
    int ret;

    ret = fstat(STDIN_FILENO, &st);
    if (ret != 0) {
        perror("stat:");
        return 1;
    }

    if (S_ISFIFO(st.st_mode)) {
        printf("stdin is fifo\n");
    } else {
        printf("stdin is not fifo\n");
    }

    return 0;
}

ターミナルエミュレータでの実行結果は

% ./stat.out
stdin is not fifo
% echo hoge | ./stat.out
stdin is fifo

Emacsからの実行は以下のようになります。

(call-process-shell-command "./stat.out" nil t) ;; => stdin is not fifo
(call-process-shell-command "echo hoge | ./stat.out" nil t) ;; => stdin is fifo

同じになりました。

おわりに

pull requestできそうならします。