How to write a cat (reading files in c)

There are typically three ways to make a commandline program read a file. The most common is probably to just append the filename as an argument to your program.

<code class="bash">programname filename.ext
</code>

But there are two other ways to do it: First a pipe into the program:

<code class="bash">cat filename.ext | programname
</code>

and second a redirected stdin:

<code class="bash">programname < filename.ext
</code>

But have you ever wondered how you make your c program work with all three input possibilities? I know I did. Turns out it is not that difficult. The hard part is actually just knowing wether your program got a input from the outside, or if it should open the file itself. Apart from that, it is just plain old fopen/fgets. This means you can write a (dumbed down) version of cat in about 30 lines of code. First the includes:

<code class="c">#include <unistd.h>
#include <stdio.h>
</code>

unistd is allows us to use isatty (more and that in a while) and stdio allows to work with files. That is all we use. The program is then split into two parts:

<code class="c">  FILE *read_this;
  if (isatty(fileno(stdin))) {
    if(argc > 1) {
      printf("filename: %s\n", argv[1]);
      read_this = fopen(argv[1], "r");
    } else {
      puts("give me a file! Either stdin, a pipe or a filename after programname");
      return 1;
    }
  }
  else {
    read_this = stdin;
  }
</code>

which is responsible for giving us the correct file to read. The function

isatty returns a bool to indicate wether a given filenumber represents a tty. If it does, we know it is not a file - and can thus proceed to try to read a filename from the program arguments. If it is not a tty, it can still be either a redirected stdin (the < version) or a pipe from another program/process (the version) - but thankfully these work in exactly the same way (for our purposes anyway). After this block of code the read_this variable will be a file pointer (or null). We then check to see if we got a valid file pointer:
<code class="c">  if (read_this == NULL) {
    fprintf(stderr, "Can't open input file\n");
    return 1;
  }
</code>

This will only happen if we try to open a file ourselves - since stdin will always be a valid file pointer. Finally we can read the content of the file, and print it to the screen (or rather stdout - it could be piped ;-) … )

<code class="c">  char buf[BUFSIZ];
  while(NULL != fgets(buf, sizeof buf, read_this)) {
      printf("%s", buf);
  }
  return 0;

</code>

That’s about it. Go see the

complete source code at this gist