Выполните действие, если ввод перенаправлен
Я хочу знать, как мне следует выполнить действие в программе на Си, если мой ввод был перенаправлен. Например, скажем, у меня есть моя скомпилированная программа "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