So, I asked here just a while ago, but half of that question was just me being dumb. And I still have issues. I hope that this will be clearer than the question before.
I'm writing POSIX cat, I nearly got it working, but I have couple of issues:
My cat can not read from a pipe and I really do not know why (redirecting (<) works fine)
I can not figure out how to make it continuously read stdin, without some issues. I had a version that worked "fine", but would create a stack-overflow. The other version wouldn't stop reading from stdin if there was only stdin i.e.: my-cat < file would read from stdin until it got terminated which it shouldn't, but it has to read from stdin and wait for termination if no files are suplied.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
char opt;
while ((opt = getopt(argc, argv, "u")) != EOF) {
switch(opt) {
case 'u':
/* Make the output un-buffered */
setbuf(stdout, NULL);
break;
default:
break;
}
}
argc -= optind;
argv += optind;
int i = 0, fildes, fs = 0;
do {
/* Check for operands, if none or operand = "-". Read from stdin */
if (argc == 0 || !strcmp(argv[i], "-")) {
fildes = STDIN_FILENO;
} else {
fildes = open(argv[i], O_RDONLY);
}
/* Check for directories */
struct stat fb;
if (!fstat(fildes, &fb) && S_ISDIR(fb.st_mode)) {
fprintf(stderr, "pcat: %s: Is a directory\n", argv[i]);
i++;
continue;
}
/* Get file size */
fs = fb.st_size;
/* If bytes are read, write them to stdout */
char *buf = malloc(fs * sizeof(char));
while ((read(fildes, buf, fs)) > 0)
write(STDOUT_FILENO, buf, fs);
free(buf);
/* Close file if it's not stdin */
if (fildes != STDIN_FILENO)
close(fildes);
i++;
} while (i < argc);
return 0;
}
Pipes don't have a size, and nor do terminals. The contents of the st_size field is undefined for such files. (On my system it seems to always contain 0, but I don't think there is any cross-platform guarantee of that.)
So your plan of reading the entire file at one go and writing it all out again is not workable for non-regular files, and is risky even for them (the read is not guaranteed to return the full number of bytes requested). It's also an unnecessary memory hog if the file is large.
A better strategy is to read into a fixed-size buffer, and write out only the number of bytes you successfully read. You repeat this until end-of-file is reached, which is indicated by read() returning 0. This is how you solve your second problem.
On a similar note, write() is not guaranteed to write out the full number of bytes you asked it to, so you need to check its return value, and if it was short, try again to write out the remaining bytes.
Here's an example:
#define BUFSIZE 65536 // arbitrary choice, can be tuned for performance
ssize_t nread;
char buf[BUFSIZE]; // or char *buf = malloc(BUFSIZE);
while ((nread = read(filedes, buf, BUFSIZE)) > 0) {
ssize_t written = 0;
while (written < nread) {
ssize_t ret = write(STDOUT_FILENO, buf + written, nread - written);
if (ret <= 0)
// handle error
written += ret;
}
}
if (nread < 0)
// handle error
As a final comment, your program lacks error checking in general; e.g. if the file cannot be opened, it will proceed anyway with filedes == -1. It is important to check the return value of every system call you issue, and handle errors accordingly. This would be essential for a program to be used in real life, and even for toy programs created just as an exercise, it will be very helpful in debugging them. (Error checking would probably have given you some clues in figuring out what was wrong with this program, for instance.)
Your cat (You can call it my-cat, but I preferred to call it felix, just permit me the pun) should be used with stdio all the time to get the benefit of the buffering done by the stdio package. Below is a simplified version of cat using exclusively stdio package (almost exactly equal as it appears in K&R) and you'll see that is completely efficient as shown (you will see that the structure is almost exactly as yours, but I simplify the processing of the data copy /like K&R book/ and the processing of arguments /yours is a bit meshy/):
felix.c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#define ERR(_code, _fmt, ...) do { \
fprintf(stderr,"%s: " _fmt, progname, \
##__VA_ARGS__); \
if (_code) exit(_code); \
} while (0)
char *progname = "cat";
void process(FILE *f);
int main(int argc, char **argv)
{
int opt;
while ((opt = getopt(argc, argv, "u")) != EOF) {
switch (opt) {
case 'u': setbuf(stdout, NULL); break;
}
}
/* for the case it has been renamed, calculate the basename
* of argv[0] (progname is used in the macro ERR above) */
progname = strrchr(argv[0], '/');
progname = progname
? progname + 1
: argv[0];
/* shift options */
argc -= optind;
argv += optind;
if (argc) {
int i;
for (i = 0; i < argc; i++) {
FILE *f = fopen(argv[i], "r");
if (!f) {
ERR(EXIT_FAILURE,
"%s: %s (errno = %d)\n",
argv[i], strerror(errno), errno);
}
process(f);
fclose(f);
}
} else {
process(stdin);
}
exit(EXIT_SUCCESS);
}
/* you don't need to complicate here, fgetc and putchar use buffering as you stated in main
* (no output buffering if you do the setbuf(NULL) and input buffering all the time). The buffer
* size is best to leave stdio to calculate it, as it queries the filesystem to get the best
* input/output size and create buffers this size. and the processing is simple with a loop like
* the one below. You'll get no appreciable difference between this and any other input/output.
* you can believe me, I've tested it. */
void process(FILE *f)
{
int c;
while ((c = fgetc(f)) != EOF) {
putchar(c);
}
}
As you see, nothing has been specially done to support redirection, as redirection is not done inside a program, but done by the program that calls it (in this case by the shell) When you start a program, you receive three already open file descriptors. These are the ones that the shell is using, or the ones that the shell just puts in the places of 0, 1, and 2 before starting your program. So your program has nothing to do to cope with redirection. Everything is done (in this case) in the shell... and this is why your program redirection works, even if you have not done anything for it to work. You have only to do redirection if you are going to call a program with its input, output or standard error redirected somewhere (and this somewhere is not the standard input, output or error you have received from your parent process)... but this is not the case of my-cat.
Related
I installed an application and its command line can do:
command -input 1.txt
command < 1.txt
echo "hello" | command
and output something. I don't have the source code and want to implement that behaviour too.
What I've tried is:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[]){
if ((fseek(stdin, 0, SEEK_END), ftell(stdin)) > 0){
rewind(stdin);
printf("stdin has data\n");
char buffer[100];
fgets(buffer, sizeof buffer, stdin);
printf("stdin data are: %s\n", buffer);
}else{
if (argc < 2){
printf("no cmd arguments\n");
return -1;
}else{
printf("command line argument: %s\n", argv[1]);
FILE* fp = fopen(argv[1], "r");
if (fp == NULL){
printf("NULL fp pointer\n");
return -1;
}
char a[100] = {0};
fgets(a, sizeof a, fp);
printf("first line of file: %s\n", a);
}
}
return 0;
}
But the problem is that pipes are not seekable. So ((fseek(stdin, 0, SEEK_END), ftell(stdin)) > 0) doesn't fit all cases.
One solution that I think of is:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[]){
if (argc > 1){
//open file with argv[1] as filename
//read data from disk file
}else{
//read data from stdin
if(stdin is file){
//get file size
//read data from stdin
}else if(stdin is pipe){
//get pipe size
//read data from stdin
}
}
return 0;
}
I have 2 problems with this code:
Is there a ispipe() function which works like isatty(fileno(stdin))? I need to tell if stdin is a pipe.
How do I get the stdin size/length from a pipe? Apparently I can't use:
fseek(stdin, 0, SEEK_END);
long size = ftell(stdin));
As #Peter pointed out in the comment, I should not try to get the stdin size from a pipe beforehand, then how do I know it reaches the end? Could anyone gives me an minimum example about this "stream-based processing"?
You can use the fstat() syscall to tell if standard input is a pipe (Either anonymous or named), or a file (And if a file, find its size):
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(void) {
struct stat s;
if (fstat(STDIN_FILENO, &s) < 0) {
perror("fstat");
return EXIT_FAILURE;
}
switch (s.st_mode & S_IFMT) {
case S_IFIFO:
puts("standard input is a pipe.");
break;
case S_IFREG:
printf("standard input is a file that is %ld bytes in size.\n",
(long)s.st_size);
break;
case S_IFCHR:
if (isatty(STDIN_FILENO)) {
puts("standard input is a terminal.");
} else {
puts("standard input is a character device.");
}
break;
default:
puts("standard input is something else.");
}
return 0;
}
Example:
$ gcc testpipe.c
$ cat testpipe.c | ./a.out
standard input is a pipe.
$ ./a.out < testpipe.c
standard input is a file that is 525 bytes in size.
$ ./a.out
standard input is a terminal.
The only way to be sure that you won't recieve more data from a pipe is when it is closed (SIGPIPE signal).
Thus, as stated in comments, allocating/reading the right of memory is challenging with pipes, since they can be infinite (e.g. /dev/random). You have to make hypothesis or use extra data in order to handle the pipe.
Depending on your use case, these strategies can be one of:
Sending the data length at the beginning of the message. This can be like: echo -e'\x05\x00\x00\x00Hello'|./myprog. With that strategy, it is trivial to read the pipe but it requieres that you know the total size of the input before you start sending it.
Allocating and reading a limited amount of data/time. If you recieve than PIPE_MAX_SIZE bytes or you wait more than TIMEOUT_PIPE, close the pipe and handle the possibly incomplete message.
Handle the message block by block. If your message follows a regular pattern, you can read it this way and handle blocks sequentially until you reach the end of the message. This also allows you to discard previous buffer to read unlimited amount of data that would not fit in memory.
I'm writing a program that when run from two separate bash sessions as two separate processes, opens a named pipe between the two to allow strings to be sent from one to the other.
When the process is first executed from one terminal, it checks stat(fname, buf) == -1 to see if a file at path fname exists and if not, creates it. The process then assumes that since it was the one to make the FIFO, it is the one that will be sending messages through it and continues accordingly.
After that occurs, the program can then be run from another terminal that should determine that it will be the receiver of messages through the pipe by checking stat(fname, buf) == -1. The condition should return false now, and stat(fname, buf) itself should return 0 because there exists a file at fname now.
But for reasons I am unable to discern, when the second process is run, stat(fname, buf) still returns -1. The variable errno is set to EFAULT. The man page for stat() only decribes EFAULT as "Bad address." Any help determining why the error occurs or what is meant by "Bad address." would be greaty appreciated.
I've verified that the file is indeed created by the first process as intended. The first process waits at the line pipe = open(fname, O_WRONLY); because it can't continue until the other end of pipe is opened.
Edit: The following is a self-contained implementation of my code. I have confirmed that it compiles and experiences the problem I described here.
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define MAX_LINE 80
#define oops(m,x) { perror(m); exit(x); }
int main(int argc, char const *argv[]) {
char line[MAX_LINE];
int pipe, pitcher, catcher, initPitcher, quit;
struct stat* buf;
char* fname = "/tmp/absFIFOO";
initPitcher = catcher = pitcher = quit = 0;
while (!quit) {
if (((!pitcher && !catcher && stat(fname, buf) == -1) || pitcher) && !quit) {
// Then file does not exist
if (errno == ENOENT) {
// printf("We're in the file does not exist part\n");
if (!pitcher && !catcher) {
// Then this must be the first time we're running the program. This process will take care of the unlink().
initPitcher = 1;
int stat;
if (stat = mkfifo(fname, 0600) < 0)
oops("Cannot make FIFO", stat);
}
pitcher = 1;
// open a named pipe
pipe = open(fname, O_WRONLY);
printf("Enter line: ");
fgets(line, MAX_LINE, stdin);
if (!strcmp(line, "quit\n")) {
quit = 1;
}
// actually write out the data and close the pipe
write(pipe, line, strlen(line));
close(pipe);
}
} else if (((!pitcher && !catcher) || catcher) && !quit) {
// The first condition is just a check to see if this is the first time we've run the program. We could check if stat(...) == 0, but that would be unnecessary
catcher = 1;
pipe = open("/tmp/absFIFO", O_RDONLY);
// set the mode to blocking (note '~')
int flags;
flags &= ~O_NONBLOCK;
fcntl(pipe, F_SETFL, flags); //what does this do?
// read the data from the pipe
read(pipe, line, MAX_LINE);
if (!strcmp(line, "quit\n")) {
quit = 1;
}
printf("Received line: %s\n", line);
// close the pipe
close(pipe);
}
}
if (initPitcher)
unlink(fname);
return 0;
}
You have this piece of code:
struct stat* buf;
...
if (((!pitcher && !catcher && stat(fname, buf) == -1)
When you call stat(), buf isn't initalized and there's no telling what it points to.
You must allocate some storage for it, so stat() has a valid place to store the result.
The easiest thing is to just allocate it on the stack:
struct stat buf;
...
if (((!pitcher && !catcher && stat(fname, &buf) == -1)
You have not shown your code, but EFAULT means 'bad address'. This indicates that you have not properly allocated (or passed) your buffer for stat or the filename (fname).
buf isn't initialised anywhere. What exactly do you expect to happen?
I am implementing my own version of the ("cat") command in Unix for practice. After i did that i became interested in implementing some flags like (-n) and (-b).
My Question: I am looking for a way to locate the blank and new lines while reading from my file. I can't remember what library or function i should use.
Here is the source code I am working on:
#include <fcntl.h>
#include <unistd.h>
static int cat_fd(int fd)
{
char buf[4096];
ssize_t nread;
while ((nread = read(fd, buf, sizeof buf)) > 0)
{
ssize_t ntotalwritten = 0;
while (ntotalwritten < nread)
{
ssize_t nwritten = write(STDOUT_FILENO, buf + ntotalwritten, nread - ntotalwritten);
if (nwritten < 1)
{
return -1;
}
ntotalwritten += nwritten;
}
}
return (nread == 0) ? 0 : -1;
}
static int cat(const char *fname)
{
int fd, success;
if ((fd = open(fname, O_RDONLY)) == -1)
{
return -1;
}
success = cat_fd(fd);
if (close(fd) != 0)
{
return -1;
}
return success;
}
int main(int argc, char **argv)
{
int i;
if (argc == 1)
{
if (cat_fd(STDIN_FILENO) != 0)
goto error;
}
else
{
for (i = 1; i < argc; i++)
{
if (cat(argv[i]) != 0)
{
goto error;
}
}
}
return 0;
error:
write(STDOUT_FILENO, "error\n", 6);
return 1;
}
Any ideas or suggestions concerning my question are greatly appreciated.
I would be even more grateful if you can type for me the complete function prototype that i shall be using as i am not an experienced programmer.
Thanks in advance for your help.
P.S: I am implementing the (-n) and (-b) flags. Thus, i am looking forward to write the line number at the beginning of each line in the file that i am reading.
While there is a function that does line-based file input in C (it's called fgets), you can't really use it for cat, because:
There's no way to know the maximum length of the line beforehand;
You'll lose portions of the input if it contains null bytes.
You'll have to look for newline symbols in your buffer after you read it, and once you find any, print the prefix of the buffer, followed by newline, line number, and the rest of the buffer (with additional processing of remaining newlines, of course).
An easier solution would be to switch to processing input one byte at a time; you can use FILE* and fgetc to use CRT-provided buffering so that you don't actually do a syscall for each read/write, or read file in blocks as you do now, and do byte processing inside the loop. Then it's a matter of writing a state machine - if a previous read character was a newline, then output a line number, unless this character is a newline and -b option is used, etc.
This still results in a less efficient solution, so you may want to treat cat without arguments specially - i.e. switch to byte-per-byte processing only if you need it. In fact, this is exactly what at least one of actual cat implementations does.
I recall reading that cat memory maps files for fast execution. Use mmap(2).
http://kernel.org/doc/man-pages/online/pages/man2/munmap.2.html
I found this example: http://ladweb.net/src/map-cat.c
I know this doesn't answer your question about newlines. I guess
memchr() would do the trick.
For my OS class I have the assignment of implementing Unix's cat command with system calls (no scanf or printf). Here's what I got so far:
(Edited thanks to responses)
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
main(void)
{
int fd1;
int fd2;
char *buffer1;
buffer1 = (char *) calloc(100, sizeof(char));
char *buffer2;
buffer2 = (char *)calloc(100, sizeof(char));
fd1 = open("input.in", O_RDONLY);
fd2 = open("input2.in", O_RDONLY);
while(eof1){ //<-lseek condition to add here
read (fd1, buffer1, /*how much to read here?*/ );
write(1, buffer1, sizeof(buffer1)-1);
}
while (eof2){
read (fd2,buffer2, /*how much to read here?*/);
write(1, buffer2, sizeof(buffer2)-1);
}
}
The examples I have seen only show read with a known number of bytes. I don't know how much bytes each of the read files will have, so how do I specify read's last paramether?
Before you can read into a buffer, you have to allocate one. Either on the stack (easiest) or with mmap.
perror is a complicated library function, not a system call.
exit is not a system call on Linux. But _exit is.
Don't write more bytes than you have read before.
Or, in general: Read the documentation on all these system calls.
Edit: Here is my code, using only system calls. The error handling is somewhat limited, since I didn't want to re-implement perror.
#include <fcntl.h>
#include <unistd.h>
static int
cat_fd(int fd) {
char buf[4096];
ssize_t nread;
while ((nread = read(fd, buf, sizeof buf)) > 0) {
ssize_t ntotalwritten = 0;
while (ntotalwritten < nread) {
ssize_t nwritten = write(STDOUT_FILENO, buf + ntotalwritten, nread - ntotalwritten);
if (nwritten < 1)
return -1;
ntotalwritten += nwritten;
}
}
return nread == 0 ? 0 : -1;
}
static int
cat(const char *fname) {
int fd, success;
if ((fd = open(fname, O_RDONLY)) == -1)
return -1;
success = cat_fd(fd);
if (close(fd) != 0)
return -1;
return success;
}
int
main(int argc, char **argv) {
int i;
if (argc == 1) {
if (cat_fd(STDIN_FILENO) != 0)
goto error;
} else {
for (i = 1; i < argc; i++) {
if (cat(argv[i]) != 0)
goto error;
}
}
return 0;
error:
write(STDOUT_FILENO, "error\n", 6);
return 1;
}
You need to read as many bytes as will fit in the buffer. Right now, you don't have a buffer yet, all you got is a pointer to a buffer. That isn't initialized to anything. Chicken-and-egg, you therefore don't know how many bytes to read either.
Create a buffer.
There is usually no need to read the entire file in one gulp. Choosing a buffer size that is the same or a multiple of the host operating system's memory page size is a good way to go. 1 or 2 X the page size is probably good enough.
Using buffers that are too big can actually cause your program to run worse because they put pressure on the virtual memory system and can cause paging.
You could use open, fstat, mmap, madvise and write to make a very efficient cat command.
If Linux specific you could use open, fstat, fadvise and splice to make an even more efficient cat command.
The advise calls are to specify the SEQUENTIAL flags which will tell the kernel to do aggressive read-ahead on the file.
If you like to be polite to the rest of the system and minimize buffer cache use, you can do your copy in chunks of 32 megabytes or so and use the advise DONTNEED flags on the parts already read.
Note:
The above will only work if the source is a file. If the fstat fails to provide a size then you must fall back to using an allocated buffer and read, write. You can use splice too.
Use the stat function to find the size of your files before you read them. Alternatively, you can read chunks until you get an EOF.
I am writing a utility which accepts either a filename, or reads from stdin.
I would like to know the most robust / fastest way of checking to see if stdin exists (data is being piped to the program) and if so reading that data in. If it doesn't exist, the processing will take place on the filename given. I have tried using the following the test for size of stdin but I believe since it's a stream and not an actual file, it's not working as I suspected it would and it's always printing -1. I know I could always read the input 1 character at a time while != EOF but I would like a more generic solution so I could end up with either a fd or a FILE* if stdin exists so the rest of the program will function seamlessly. I would also like to be able to know its size, pending the stream has been closed by the previous program.
long getSizeOfInput(FILE *input){
long retvalue = 0;
fseek(input, 0L, SEEK_END);
retvalue = ftell(input);
fseek(input, 0L, SEEK_SET);
return retvalue;
}
int main(int argc, char **argv) {
printf("Size of stdin: %ld\n", getSizeOfInput(stdin));
exit(0);
}
Terminal:
$ echo "hi!" | myprog
Size of stdin: -1
You're thinking it wrong.
What you are trying to do:
If stdin exists use it, else check whether the user supplied a filename.
What you should be doing instead:
If the user supplies a filename, then use the filename. Else use stdin.
You cannot know the total length of an incoming stream unless you read it all and keep it buffered. You just cannot seek backwards into pipes. This is a limitation of how pipes work. Pipes are not suitable for all tasks and sometimes intermediate files are required.
First, ask the program to tell you what is wrong by checking the errno, which is set on failure, such as during fseek or ftell.
Others (tonio & LatinSuD) have explained the mistake with handling stdin versus checking for a filename. Namely, first check argc (argument count) to see if there are any command line parameters specified if (argc > 1), treating - as a special case meaning stdin.
If no parameters are specified, then assume input is (going) to come from stdin, which is a stream not file, and the fseek function fails on it.
In the case of a stream, where you cannot use file-on-disk oriented library functions (i.e. fseek and ftell), you simply have to count the number of bytes read (including trailing newline characters) until receiving EOF (end-of-file).
For usage with large files you could speed it up by using fgets to a char array for more efficient reading of the bytes in a (text) file. For a binary file you need to use fopen(const char* filename, "rb") and use fread instead of fgetc/fgets.
You could also check the for feof(stdin) / ferror(stdin) when using the byte-counting method to detect any errors when reading from a stream.
The sample below should be C99 compliant and portable.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
long getSizeOfInput(FILE *input){
long retvalue = 0;
int c;
if (input != stdin) {
if (-1 == fseek(input, 0L, SEEK_END)) {
fprintf(stderr, "Error seek end: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (-1 == (retvalue = ftell(input))) {
fprintf(stderr, "ftell failed: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (-1 == fseek(input, 0L, SEEK_SET)) {
fprintf(stderr, "Error seek start: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
} else {
/* for stdin, we need to read in the entire stream until EOF */
while (EOF != (c = fgetc(input))) {
retvalue++;
}
}
return retvalue;
}
int main(int argc, char **argv) {
FILE *input;
if (argc > 1) {
if(!strcmp(argv[1],"-")) {
input = stdin;
} else {
input = fopen(argv[1],"r");
if (NULL == input) {
fprintf(stderr, "Unable to open '%s': %s\n",
argv[1], strerror(errno));
exit(EXIT_FAILURE);
}
}
} else {
input = stdin;
}
printf("Size of file: %ld\n", getSizeOfInput(input));
return EXIT_SUCCESS;
}
You may want to look at how this is done in the cat utility, for example.
See code here.
If there is no filename as argument, or it is "-", then stdin is used for input.
stdin will be there, even if no data is pushed to it (but then, your read call may wait forever).
You can just read from stdin unless the user supply a filename ?
If not, treat the special "filename" - as meaning "read from stdin". The user would have to start the program like cat file | myprogram - if he wants to pipe data to it, and myprogam file if he wants it to read from a file.
int main(int argc,char *argv[] ) {
FILE *input;
if(argc != 2) {
usage();
return 1;
}
if(!strcmp(argv[1],"-")) {
input = stdin;
} else {
input = fopen(argv[1],"rb");
//check for errors
}
If you're on *nix, you can check whether stdin is a fifo:
struct stat st_info;
if(fstat(0,&st_info) != 0)
//error
}
if(S_ISFIFO(st_info.st_mode)) {
//stdin is a pipe
}
Though that won't handle the user doing myprogram <file
You can also check if stdin is a terminal/console
if(isatty(0)) {
//stdin is a terminal
}
Just testing for end of file with feof would do, I think.
Note that what you want is to know if stdin is connected to a terminal or not, not if it exists. It always exists but when you use the shell to pipe something into it or read a file, it is not connected to a terminal.
You can check that a file descriptor is connected to a terminal via the termios.h functions:
#include <termios.h>
#include <stdbool.h>
bool stdin_is_a_pipe(void)
{
struct termios t;
return (tcgetattr(STDIN_FILENO, &t) < 0);
}
This will try to fetch the terminal attributes of stdin. If it is not connected to a pipe, it is attached to a tty and the tcgetattr function call will succeed. In order to detect a pipe, we check for tcgetattr failure.