This item was added on: 2003/03/22
Most simple programs accept input from the standard input and write to standard output, with command line redirection these programs can also read from and write to files on the hard disk as well, but what if a program needs to read input from both stdin
and a file? Redirection doesn't work this way and the program must handle the file itself. Consider a program that copies one file to another with a command line argument like this:
pcopy file1 file2
#include <stdio.h>
#include <stdlib.h>
static FILE *open_file ( char *file, char *mode )
{
FILE *fp = fopen ( file, mode );
if ( fp == NULL ) {
perror ( "Unable to open file" );
exit ( EXIT_FAILURE );
}
return fp;
}
int main ( int argc, char *argv[] )
{
int ch;
FILE *in;
FILE *out;
if ( argc != 3 ) {
fprintf ( stderr, "Usage: %s <readfile1> <writefile2>\n", argv[0] );
exit ( EXIT_FAILURE );
}
in = open_file ( argv[1], "r" );
out = open_file ( argv[2], "w" );
while ( ( ch = fgetc ( in ) ) != EOF )
fputc ( ch, out );
fclose ( in );
fclose ( out );
return EXIT_SUCCESS;
}
First, two files are opened with the open_file
function that performs error checking on the standard library function fopen
, which does the real work. If a file cannot be opened, open_file
reports an error and terminates the program.Once the files are open, a while loop consisting of a call to fgetc
to read a single character from in is entered. The loop will continue to read from in and write that character to out until the value of EOF
is reached. EOF
is a macro defined in stdio.h
.
You'll notice that fopen
takes two arguments, the file name as a string and a second string designated as the open mode. This determines how the file is to be used, for example reading or writing or both. The open modes for text files are:
"r"
"w"
"a"
"r+"
"w+"
"a+"
Binary files are differentiated from text files by a trailing "b" in the open mode and are most commonly (unfortunately) operated on with fread and fwrite:
#include <stdio.h>
#include <stdlib.h>
int main ( void )
{
FILE *in;
char buf[BUFSIZ];
in = fopen ( "input.txt", "rb" );
if ( in == NULL ) {
perror ( "Unable to open the file" );
exit ( EXIT_FAILURE );
}
fread ( buf, 1, sizeof buf, in );
printf ( "%s\n", buf );
fclose ( in );
return 0;
}
The problem with this is that binary files are by nature nonportable and fread/fwrite
do not work to alleviate this problem in the slightest. So while binary files tend to be smaller and faster, working with them in a portable manner is difficult to get right and can be very tedious. Consider a program that simply reads a four byte integer in little endian order from a binary file:
#include <stdio.h>
#include <stdlib.h>
int main ( void )
{
FILE *in;
int num;
in = fopen ( "input.txt", "rb" );
if ( in == NULL ) {
perror ( "Unable to open the file" );
exit ( EXIT_FAILURE );
}
num = getc ( in );
num |= (unsigned long)getc ( in ) << 8;
num |= (unsigned long)getc ( in ) << 16;
num |= (unsigned long)getc ( in ) << 24;
printf ( "%d\n", num );
fclose ( in );
return 0;
}
As you can see, this makes binary files seem less attractive when compared to text files. In my experience, the benefits of text files far outweigh the benefits of binary files in practical usage. Files are very easy to use when you consider that stdin
, stdout
, and stderr
are all FILE pointers as well, you've been using them since your first day programming. The only difference between the standard streams and user defined streams is that you have to open and close them explicitly in your program.The definitions of the file I/O functions are:
FILE *fopen ( const char *filename, const char *mode );
int fclose ( FILE *stream );
int fprintf( FILE *stream, const char *format, ... );
int fscanf ( FILE *stream, const char *format, ... );
int fgetc ( FILE *stream );
char *fgets ( char *s, int n, FILE *stream );
int fputc ( int c, FILE *stream );
int fputs ( const char *s, FILE *stream );
int getc ( FILE *stream );
int putc ( int c, FILE *stream );
int ungetc ( int c, FILE *stream );
size_t fread ( void *ptr, size_t size, size_t nmemb, FILE *stream );
size_t fwrite ( const void *ptr, size_t size, size_t nmemb, FILE *stream );
More can be done with files such as moving to a specific offset or setting the input buffer size, or even creating and removing temporary files, but such operations will be discussed later to keep this "How-To" short. Credit: Prelude