Use piped input like it's from stdin in a C++ program

Alec Jacobson

September 06, 2011

weblog/

I was dismayed to find out that the file pointer stdin works fine in a compiled C++ program from a unix/linux shell if you redirect a file to standard in like this: Option #1:
./my_program <myinputfile
But it doesn't work if the input is coming from a pipe like this: Option #2:
cat myfile | ./myprogram
Now, there exist ways to read from the pipe correctly. It seems you should first determine which situation is occurring and process each situation differently. But for me this was not an option because I wanted to call a library function whose interface looked like this:
int lib_func(FILE * file_pointer);
For option #1, just calling the following works just fine:
lib_func(stdin);
But for option #2, I get a segmentation fault. So I whipped up this small function you can save in stdin_to_temp.h:
#include <cstdio>
// Write stdin/piped input to a temporary file which can than be preprocessed as it
// is (a normal file).
// Outputs:
//   temp_file  pointer to temp file pointer, rewound to beginning of file so
//     its ready to be read
// Return true only if no errors were found
//
// Note: Caller is responsible for closing the file (tmpfile() automatically
// unlinks the file so there is no need to remove/delete/unlink the file)
bool stdin_to_temp(FILE ** temp_file);

// IMPLEMENTATION
#include <iostream>
using namespace std;

bool stdin_to_temp(FILE ** temp_file)
{
  // get a temporary file
  *temp_file = tmpfile();
  if(*temp_file == NULL)
  {
    fprintf(stderr,"IOError: temp file could not be created.\n");
    return false;
  }
  char c;
  // c++'s cin handles the stdind input in a reasonable way
  while (cin.good())
  {
    c = cin.get();
    if(cin.good())
    {
      if(1 != fwrite(&c,sizeof(char),1,*temp_file))
      {
        fprintf(stderr,"IOError: error writing to tempfile.\n");
        return false;
      }
    }
  }
  // rewind file getting it ready to read from
  rewind(*temp_file);
  return true;
}
The idea is to take advantage of the fact that in C++ the istream cin "correctly" treats stdin and piped input as the same. So if I read from cin and write it back to some temporary file then I get a file pointer that acts like how I wanted the stdin to act. You can demo how it works with this small program. Save it in stdin_to_temp_demo.cpp
#include "stdin_to_temp.h"

/**
 * Compile with:
 *   g++ -O3 -o stdin_to_temp_demo stdin_to_temp_demo.cpp
 *
 * Run examples:
 *   cat file1 | ./stdin_to_temp_demo
 *   cat file1 | ./stdin_to_temp_demo | cat >file2
 *   cat file1 | ./stdin_to_temp_demo dummy1 dummy2 | cat >file2
 *   ./stdin_to_temp_demo <file1 | cat >file2
 *   ./stdin_to_temp_demo <file1 >file2
 *
 */

int main(int argc,char * argv[])
{
  // Process arguements and print to stderr
  for(int i = 1;i<argc;i++)
  {
    fprintf(stderr,"argv[%d] = %s\n",i,argv[i]);
  }

  FILE * temp_file;
  bool success = stdin_to_temp(&temp_file);
  if(!success)
  {
    fprintf(stderr,"Fatal Error: could not convert stdin to temp file\n");
    // try to close temp file
    fclose(temp_file);
    exit(1);
  }

  // Do something interesting with the temporary file. 
  // Read the file and write it to stdout
  char c;
  // Read file one character at a time and write to stdout
  while(fread(&c,sizeof(char),1,temp_file)==1)
  {
    fwrite(&c,sizeof(char),1,stdout);
  }
  // close file
  fclose(temp_file);
}
Surely this doesn't not take full advantage of pipes. I think its actually defeating their intended purpose. But it allows me to have the familiar interface for my simple C++ program that I wanted.