Выполните действие, если ввод перенаправлен

Я хочу знать, как мне следует выполнить действие в программе на Си, если мой ввод был перенаправлен. Например, скажем, у меня есть моя скомпилированная программа "prog", и я перенаправляю на нее вход "input.txt" (я делаю ./prog < input.txt).

Как я могу обнаружить это в коде?

1 ответ

Решение

В общем, вы не можете сказать, был ли ввод перенаправлен; но вы можете различить по типу файла stdin. Если не было перенаправления, это будет терминал; или это могло быть установлено как труба cat foo | ./prog или перенаправление из обычного файла (как ваш пример), или перенаправление из одного из нескольких типов специальных файлов (./prog </dev/sda1 перенаправление его из специального файла блока и т. д.).

Итак, если вы хотите определить, является ли stdin терминалом (TTY), вы можете использовать isatty:

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

int main(int argc, char **argv) {
    if (isatty(STDIN_FILENO))
        printf("stdin is a tty\n");
    else
        printf("stdin is not a tty\n");

    return 0;
}

Если вы хотите различать другие случаи (например, канал, специальный файл блока и т. Д.), Вы можете использовать fstat извлечь немного больше информации о типе файла. Обратите внимание, что это на самом деле не говорит вам, является ли это терминалом, вам все еще нужно isatty для этого (который является оберткой вокруг ioctl который получает информацию о терминале, по крайней мере, в Linux). Вы можете добавить следующее в вышеуказанную программу (наряду с #include <sys/stat.h>) чтобы получить дополнительную информацию о том, что это за файл.

if (fstat(STDIN_FILENO, &sb) == 0) {
    if (S_ISBLK(sb.st_mode))
        printf("stdin is a block special file\n");
    else if (S_ISCHR(sb.st_mode))
        printf("stdin is a character special file\n");
    else if (S_ISDIR(sb.st_mode))
        printf("stdin is a directory\n");
    else if (S_ISFIFO(sb.st_mode))
        printf("stdin is a FIFO (pipe)\n");
    else if (S_ISREG(sb.st_mode))
        printf("stdin is a regular file\n");
    else if (S_ISLNK(sb.st_mode))
        printf("stdin is a symlink\n");
    else if (S_ISSOCK(sb.st_mode))
        printf("stdin is a socket\n");
} else {
    printf("failed to stat stdin\n");
}

Обратите внимание, что вы никогда не увидите символическую ссылку из перенаправления, так как оболочка уже разыменовала символическую ссылку перед открытием файла от имени вашей программы; и я не мог заставить Bash открыть сокет домена Unix. Но все остальное возможно, включая, на удивление, каталог.

$ ./isatty 
stdin is a tty
stdin is a character special file
$ ./isatty < ./isatty
stdin is not a tty
stdin is a regular file
$ sudo sh -c './isatty < /dev/sda'
stdin is not a tty
stdin is a block special file
$ sudo sh -c './isatty < /dev/console'
stdin is a tty
stdin is a character special file
$ cat isatty | ./isatty 
stdin is not a tty
stdin is a FIFO (pipe)
$ mkfifo fifo
$ echo > fifo &  # Need to do this or else opening the fifo for read will block
[1] 27931
$ ./isatty < fifo
stdin is not a tty
stdin is a FIFO (pipe)
[1]+  Done                    echo > fifo
$ ./isatty < .
stdin is not a tty
stdin is a directory
$ socat /dev/null UNIX-LISTEN:./unix-socket &
[1] 28044
$ ./isatty < ./unix-socket 
bash: ./unix-socket: No such device or address
$ kill $!
[1]+  Exit 143                socat /dev/null UNIX-LISTEN:./unix-socket
$ ln -s isatty symlink
$ ./isatty < symlink
stdin is not a tty
stdin is a regular file
$ ln -s no-such-file broken-link
$ ./isatty < broken-link 
bash: broken-link: No such file or directory
Другие вопросы по тегам