I want to know how I should go about performing an action in a C program if my input was redirected. For example, say I have my compiled program "prog" and I redirect an input "input.txt" to it (I do ./prog < input.txt
).
How do I detect this in code?
You can't, in general, tell if input has been redirected; but you can distinguish based on what type of file stdin is. If there has been no redirection, it will be a terminal; or it could have been set up as a pipe cat foo | ./prog
, or a redirection from a regular file (like your example), or a redirection from one of a number of types of special files (./prog </dev/sda1
redirecting it from a block special file, etc).
So, if you want to determine if stdin is a terminal (TTY), you can use 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;
}
If you want to distinguish between other cases (like a pipe, block special file, etc), you can use fstat
to extract some more file type information. Note that this doesn't actually tell you whether it's a terminal, you still need isatty
for that (which is a wrapper around an ioctl
that fetches information about a terminal, at least on Linux). You can add the following to the above program (along with #include <sys/stat.h>
) to get extra information about what kind of file stdin is.
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");
}
Note that you will never see a symlink from a redirection, as the shell will have already dereferenced the symlink before opening the file on behalf of your program; and I couldn't get Bash to open a Unix domain socket either. But all of the rest are possible, including, surprisingly, a directory.
$ ./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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With