简体   繁体   中英

Conditionally reading file from std input or a file

Newbie C++ programmer here. I'm trying to write a command line application that takes two arguments, an input file and an output file. However, if the input file or the output file name is "-", I need the program to read/output to standard input/output instead. My problem is that in C++, I don't know how to do this without the compiler not knowing that the input/output streams are initialized. Here's the code I have.

if(argv[1] == "-") {
  istream input;
  cout << "Using standard input" << endl;
}
else {
  ifstream input;
  cout << "Using input file " << argv[1] << endl;
  input.open(argv[1], ios::in);
  if(!input) {
    cerr << "Cannot open input file '" << argv[1]
    << "', it either does not exist or is not readable." << endl;
    return 0;
  }
}

if(argv[2] == "-") {
  ostream output;
  cout << "Using standard output" << endl;
}
else {
  ofstream output;
  cout << "Using output file " << argv[2] << endl;
  output.open(argv[2], ios::out);

  if(!output) {
    cerr << "Cannot open output file '" << argv[2] << "' for writing."
    << " Is it read only?" << endl;
    return 0;
  }
}

From here I cannot call the operator >> on input, because, I'm guessing, the compiler doesn't know it's been initialized.

You can use a reference to a stream, and then initialize it to refer to either a file stream or standard input or output. The initialization has to happen in a single command, though, so you'll have to declare the file streams even if you don't use them.

ifstream file_input;
istream& input = (strcmp(argv[1], "-") == 0) ? cin : file_input;

ofstream file_output;
ostream& output = (strcmp(argv[2], "-") == 0) ? cout : file_output;

Notice the & in the declarations of input and output . They indicate that we're not declaring a separate stream object, but rather just declaring a reference to some other stream object, which we select conditionally based on the value of argv[x] .

Then you can open the files, if you need to. The drawback is that we need to check for "-" strings twice instead of just once for each input or output.

if (strcmp(argv[1], "-") == 0) {
  cout << "Using standard input" << endl;
} else {
  cout << "Using input file " << argv[1] << endl;
  file_input.open(argv[1]);
  if (!file_input) {
    cerr << "Cannot open input file '" << argv[1]
         << "', it either does not exist or is not readable." << endl;
    return 1;
  }
}

Thereafter, you can read from input and write to output , and the files or the standard I/O streams will be used.


Note the other changes I made to your code. First, I call strcmp instead of using the == operator; the operator doesn't do what you think it does when comparing a char* with a literal. Next, when opening the file fails, I return 1 instead of 0. Zero indicates a successful program, whereas non-zero tells the OS that the program failed.

You can declare the members outside your conditionals, since ifstream inherits istream and ofstream inherits ostream . To avoid slicing, use pointers:

istream* input = NULL;
bool isFile = false;
if(argv[1] == "-") {
  input = new istream;
}
else {
  input = new ifstream;
  isfile = true;
}

Then, wherever you want to use input , you just cast it to the right type:

if (isFile)
{
    ifstream* finput = (ifstream*)input;
}

This is not the only solution; there probably are cleaner ones

The thing is, you have to declare the stream outside of the block inside your conditional, so it doesn't go out of scope, since you want to use it outside.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM