- Back to Home »
- LETS LEARN C,COURSE-9
Posted by : ANIMESH SHAW
Saturday, 17 September 2011
§14 File IO Routines and Command Line Arguments
When your C program starts up, it automatically creates three file streams for you. These are known as
stdin, stdout and stderr, which are usually the keyboard, the terminal and the terminal again respectively. If you have included (which you should have) the symbols stdin, stdout and stderr are available for your use. The functions from that have seen so far, like printf, write to stdout. Others, like scanf, read from stdin. Here is an example of using scanf to read keyboard input.
/*---- Keyboard Input C Example ("input.c") ----------------------------------*/
/* ANSI C Headers */
#include
#include
/* Main Program starts here */
int main( int argc, char *argv[] )
{
int i = 0;
float f = 0.0;
char string[80] = {'\0'};
/* End of declarations ... */
printf("Enter a string, a decimal and a real number separated by spaces\n");
scanf("%s %d %f", string, &i, &f); /* Not good - no string length check */
printf("You entered \"%s\", %d and %f\n", string, i, f);
exit(EXIT_SUCCESS);
}
Compile the program and enter some data. Here is some example input and output.
Enter a string, a decimal and a real number separated by spaces
Hello 4 3.14159
You entered "Hello", 4 and 3.141590
Each item is delimited by whitespace (which includes new lines, of course), but you can use a scanset format specifier to overcome this, "%[characters_wanted]". See the DEC C Run-Time Library Reference Manual, Chapter 2, and Table 2-3, and K&R II page 246 for more information on this. The scanf function actually returns an integer value, which is the number of items successfully read in, or the predefined macro value EOF if an error occurred.Because you can't safely limit string input with
scanf (which means you could unintentionally overwrite important memory locations and cause your program to crash by entering a string longer than the memory allocated for it), it is far better to use fgets().What you do is read a limited length string with
fgets(), the prototype for which is char *fgets( char *str, int maxchar, FILE *file_ptr). So if we had a first argument, destination_string, declared as char destination_string[STRING_SIZE], we would use sizeof(destination_string) for maxchar, and stdin as the input FILE stream.
.
char destination_string[STRING_SIZE];
char *pszResult;
.
pszResult = fgets( destination_string, sizeof(destination_string), stdin );
if ( !pszResult )
{
/* Error or EOF (End Of File) */
.
}
else
{
if ( destination_string[strlen(destination_string)-1] != '\n' )
{
/* Length limit means we didn't get all the input string */
.
}
else
{
/* Got it all - do sscanf() or whatever */
.
}
}
The
fgets() function stops reading after the first newline character is encountered, or, if no newline is found, it reads in at most maxchar-1 characters. In either case the string is terminated with '\0'. You can tell whether the length limit cut in by checking if the last character in the string is '\n'. If it isn't, then you may need to read more input. This will requiremalloc()ing space enough for all the "segments" of the string and perhaps strncat()ing them together. This is left as an exercise for the reader. The resultant string is then parsed with sscanf(), which takes its input from a string; destination_string in this description.Failure to limit the length of an input string led to the infamous "finger" bug. There is a function similar to
fgets, called gets(), which was used in the original finger program (finger is a program which returns information about users on the target system). Never, repeat never, use gets. It reads characters into the string pointed to by its argument, with no length check. This was exploited to overwrite the return address on the stack, and make the "privileged" finger image execute some code (sent as a part of a long string) to create a command shell running with full privileges. Programming Challenge 9
_______________________
Write a program using scanf, or fgets and sscanf. Try out
different conversion characters, check the effects of giving bad
data.
The standard C file system is based on streams, which are rather like unit numbers in Fortran. Streams connect to devices, such as files on disks, terminals or printers. By using file streams, you are shielded from having to do the low level IO. C recognises two types of stream which are text streams and binary streams. Binary streams are written to and read from without any mucking about ! Text streams can have, or not have, implementation defined line feeds and carriage returns. Binary streams are usually used for writing out data in the machine's internal representation, like an array of structures for example. The type is determined when the file is opened with fopen(). The following table show the access modes you can use with fopen().r Open text file reading.
w Open or create text file for writing, discard previous contents.
(creates new version under VMS)
a Open or create for appending, write at end of existing data.
r+ Open text file reading AND writing.
w+ Open or create text file for update, discard previous contents.
(creates new version under VMS)
a+ Open or create for appending, write at end of existing data.
Add a "b" after the access mode letter, and we are talking binary files. Here is an example program to write and read back an array of data structure, which will make everything remarkably clear.
/*---- File IO Example ("fileio.c") ------------------------------------------*/
/*---- Put all #include files here -------------------------------------------*/
/* ANSI Headers */
#include /* Character macros */
#include /* errno error codes */
#include /* Standard I/O */
#include /* Standard Library */
#include /* String Library */
#include /* Time Library */
/*---- Put all #define statements here ---------------------------------------*/
#define PROGRAM_VERSION "1.6"
#define TYPE_OF_FILE "TEST_DATA"
#define NDATA_POINTS 10
/*---- Put all structure definitions here ------------------------------------*/
/* Following structures MUST be packed tightly ie. no member alignment -------*/
#ifdef __DECC
#pragma member_alignment save
# pragma nomember_alignment
#endif
struct file_header_s {
char type[32];
char version[8];
char creator[20];
time_t time;
};
struct data_s {
short x;
short y;
char name[8];
};
#ifdef __DECC
# pragma member_alignment restore
#endif
int main(int argc, char *argv[])
{
struct data_s *data_ptr = NULL;
struct file_header_s file_header;
FILE *outfile = NULL;
FILE *infile = NULL;
int i = 0, got_answer = 0;
char answer[8], yeno[4], node_user[20];
char filename[FILENAME_MAX] = {'\0'};
char *c_ptr = NULL;
long int ndata = 0, nitems = 0;
/* End of declarations ... */
/* Set up a default name in case user hasn't specified one */
if (argc < 2)
{
strncpy(filename,"MYDATA.DAT",sizeof(filename)/sizeof(filename[0]) - 1);
}
else
{
strncpy(filename,argv[1],sizeof(filename)/sizeof(filename[0]) - 1);
}
/* Get node name and user name (no length check on node_user - this is bad) */
#if !defined( _WIN32 )
sprintf( node_user, "%s%s", getenv("SYS$NODE"), getenv("USER") ); /* VMS */
#else
sprintf( node_user,"\\\\%s\\%s",getenv("COMPUTERNAME"),getenv("USERNAME"));
#endif
/* Convert to uppercase */
c_ptr = node_user;
while ( *c_ptr = toupper(*c_ptr) ) ++c_ptr; /* toupper lives in */
/* Allocate data space and initialize it */
data_ptr = (struct data_s *)malloc( sizeof(struct data_s)*NDATA_POINTS );
if ( data_ptr )
{
ndata = NDATA_POINTS;
for ( i = 0; i < ndata; i++) {
data_ptr[i].x = data_ptr[i].y = i;
sprintf(data_ptr[i].name, "%3.3d,%3.3d", data_ptr[i].x, data_ptr[i].y );
}
/* Open the file for writing in binary mode */
outfile = fopen(filename,"wb");
if ( outfile != NULL )
{
/* Set up the header */
strcpy(file_header.type,TYPE_OF_FILE);
strcpy(file_header.version,PROGRAM_VERSION);
sprintf(file_header.creator,"%s",node_user);
(void)time(&file_header.time);
printf("Writing out the header\n");
/* Items Written Data Pointer Size in bytes No. of items stream */
/* | | | | | */
/* v v v v v */
nitems = fwrite( &file_header, sizeof(file_header), 1, outfile);
if ( ferror(outfile) )
{
fprintf(stderr,"Error writing file 'header':\n%s",strerror(errno));
}
printf("Writing out the number of data items, %d\n", ndata);
nitems = fwrite( &ndata, sizeof(ndata), 1, outfile);
if ( ferror(outfile) )
{
fprintf(stderr,"Error writing number of data items:\n%s",
strerror(errno));
}
printf("Writing out the actual data data all in one chunk\n");
nitems = fwrite( data_ptr, sizeof(struct data_s)*ndata, 1, outfile);
if ( ferror(outfile) )
{
fprintf(stderr,"Error writing data:\n%s",strerror(errno));
}
printf("Closing output file\n");
fclose(outfile);
} else {
fprintf(stderr,"Error creating data file %s:\n%s", filename,
strerror(errno));
}
} else {
fprintf(stderr, "Couldn't allocate space for %d data structures\n",ndata);
}
/* Now optionally read the data back in and format */
do {
printf("\nRead data back in ? [Y/N]: ");
fgets( yeno, sizeof(yeno) , stdin); /* Reads in sizeof(yeno)-1 chars */
got_answer = sscanf( yeno, "%[YyNnTtFf]", answer);
} while ( !got_answer );
if ( answer[0] == 'Y' || answer[0] == 'y' ||
answer[0] == 'T' || answer[0] == 't' )
{
printf( "Here we go ..\n" );
/* Zero out the structures just to show there's no cheating */
ndata = 0;
memset( &file_header, 0 , sizeof( file_header ) );
memset( data_ptr, 0 , sizeof(struct data_s)*NDATA_POINTS );
/* Open the file for reading in binary mode */
infile = fopen(filename,"rb");
if ( infile != NULL )
{
printf("Reading in the header\n");
nitems = fread(&file_header,sizeof(file_header),1,infile);
if ( ferror(infile) )
{
fprintf(stderr,"Error reading file 'header':\n%s",strerror(errno));
}
printf("Header information: file type %s\n", file_header.type );
printf(" version %s\n", file_header.version );
printf(" created by %s\n", file_header.creator );
printf(" on %s\n", ctime( &file_header.time ) );
nitems = fread( &ndata, sizeof(ndata), 1, infile);
printf("Read in the number of data items, %d\n", ndata);
if ( ferror(infile) )
{
fprintf(stderr,"Error reading number of data items:\n%s",
strerror(errno));
}
printf("Reading in the actual data data all in one chunk\n");
nitems = fread( data_ptr, sizeof(struct data_s)*ndata, 1, infile);
if ( ferror(infile) )
{
fprintf(stderr,"Error reading data:\n%s",strerror(errno));
}
printf("Closing intput file\n\n");
fclose(infile);
printf("Read in %d data items\n", ndata);
for ( i = 0; i < ndata; i++) {
printf("%3d) x:%3d, y:%3d, Label: %s\n",
i, data_ptr[i].x, data_ptr[i].y, data_ptr[i].name );
}
} else {
fprintf(stderr,"Error opening data file %s:\n%s", filename,
strerror(errno));
}
} else {
printf( "OK - be like that\n" );
}
exit(EXIT_SUCCESS);
}
The strange
#pragma directive is a standard way to do non-standard things, like instruct the compiler to close-pack the data (i.e. don't use natural alignment) and is explained in §17. The "f" routines you can look up yourself in a book. Several DEC system and library routines are used, so the VMS headers , and are included. Notice that the function calls are lowercase, and there are no prototypes defined yet, so you are on your own there if you get an argument wrong ! This should change with future releases of DEC C. The strerror routine is useful for getting a text error message. After many library calls, not just stdio calls, an integer expression, errno, defined in , yields a non-zero value if an error occurs. Be very careful making assumptions about errno, because in many cases it isn't actually a variable but a macro, which allows it, for example, to behave in a thread-safe manner. This means, however, that it isn't safe to treat it as a global integer variable and take it's address and so forth.The
strerror function from converts the error number into a text string, and the function value is a pointer to this string. You must not modify this string, and it will be overwritten by later calls to strerror. This program reads in many bytes at a time with each read, but there is a function, fgetc, to read a single character, and a matching function, ungetc which returns the last read character to the input stream to be read by the next fgetc. This provides a kind of look-ahead function which is exploited by the next example program, "calc.c", provided by Neill Clift. In this program, getchar is used instead of fgetc. It is equivalent to fgetc except that it reads from stdin. This sturdy example is adapted from the very expression parser used by LID (a Y.R.L. replacement for DEC's CDD).
/*---- Calculator expression evaluator example ("calc.c") --------------------*/
/*
History:
Version Name Date
V01-001 Neill Clift 16-Mar-1995
Initial version
*/
/* ANSI Headers */
#include
#include
#include
/* Function prototypes */
int parse_expression(void);
int parse_expression_factor(void);
int parse_expression_term(void);
int parse_literal(void);
int getnonwhite(void);
int match_token(int tomatch);
/* Start of main program */
int main( int argc, char *argv[])
{
int val = 0;
/* End of declarations ... */
printf("Enter expression terminated by ;\n");
printf("Calc> ");
val = parse_expression();
if (match_token(';'))
{
printf("Result is %d\n",val);
} else {
printf("Expression seems bust\n");
}
exit(EXIT_SUCCESS);
}
/*---- Get the next character skipping white space ---------------------------*/
int getnonwhite(void)
{
int c = 0;
/* End of declarations ... */
while (1)
{
c = getchar();
if (c == EOF)
{
break;
}
else if (!isspace(c))
{
break;
}
}
return( c );
}
/*--- Match single character against next input char. If match then gobble ---*/
/*--- it up. If we don't match it then push it back for future matches -------*/
int match_token(int tomatch)
{
int c;
/* End of declarations ... */
c = getnonwhite();
if (c == tomatch)
{
return( 1 );
}
else
{
ungetc(c,stdin); /* Put character back on input stream to be read again */
return( 0 );
}
}
/*---- Parse a single number from input +/- nnnnn ---------------------------*/
int parse_literal(void)
{
int retval,st;
/* End of declarations ... */
retval = 0;
st = scanf("%d",&retval);
if (st == EOF)
{
printf("Hit EOF looking for literal\n");
}
else if (st == 0)
{
printf("Missing literal\n");
}
return( retval );
}
/*---- Syntax is: Literal or: (expression) ---------------------------*/
int parse_expression_factor(void)
{
int retval;
/* End of declarations ... */
if (match_token('('))
{
retval = parse_expression();
if (!match_token(')'))
{
printf("Missing close bracket\n");
}
}
else
{
retval = parse_literal();
}
return( retval );
}
/*---- Parse an expression term, Syntax: {} -*/
int parse_expression_term(void)
{
int tmp,mul,opr;
/* End of declarations ... */
tmp = parse_expression_factor();
while (1)
{
if (match_token('*'))
{
opr = 1;
}
else if (match_token('/'))
{
opr = -1;
}
else
{
break;
}
mul = parse_expression_factor();
if (opr == 1)
{
tmp = tmp * mul;
}
else if (mul == 0)
{
printf("Division by zero!\n");
}
else
{
tmp = tmp / mul;
}
}
return( tmp );
}
/*---- Parse an expression, Syntax: [+/-]{} -----------*/
int parse_expression(void)
{
int tmp,mul,add;
/* End of declarations ... */
/* Check for leading + or -. None means plus */
if (match_token('+'))
{
mul = 1;
}
else if (match_token('-'))
{
mul = -1;
}
else
{
mul = 1;
}
tmp = parse_expression_term();
tmp = tmp * mul;
while (1)
{
if (match_token('+'))
{
mul = 1;
}
else if (match_token('-'))
{
mul = -1;
}
else
{
break;
}
add = parse_expression_term();
tmp = tmp + mul * add;
};
return( tmp );
}
A few other file routines are worth mentioning. These are
fgetpos, fsetpos and fseek, which generally apply to files open in binary mode. They allow you to position to a particular byte within a file, specified from the current position, or the beginning or end of the file. The fgetpos function returns the position in an object of type fpos_t, which is only meaningful when used with fsetpos. See K&R II page 248 for more details. The fflush function allows you to flush cached data on an output stream if you want to do this before fclose, which flushes anyway, as does exit(). To make sure that data is actually written to disk you must call a non-standard function like fsync after fflush - fflush on it's own doesn't guarantee that the data has actually been written to permanent storage. Streams can be redirected using freopen, and this is a commonly used method for making stdout get written to a file without having to change printf calls. Testing for end of file is achieved by using the feof function.A standard method for getting command line arguments is provided in C. These are the arguments to the main program,
argc and argv. The integer argc is the number of command line arguments, and must be greater than or equal to zero. The second argument, argv, is an array of pointers to characters. If argc is zero, then argv[0] must be the NULL pointer. On most implementations, it will be greater than zero, and argv[0] points to the program name. On some machines this will be a string like "myprog". On VMS or Windows NT systems it is the full file specification. If the program name is not available, argv[0] must point to the null character, '\0'. The elements argv[1] to argv[argc-1], if they exist, point to strings which are implementation defined. In practise, these are usually the whitespace separated (unless "quoted") arguments supplied on the command line. Under VMS, command line arguments are converted to lowercase, unless quoted. The following example, "args.c", shows how to get the command line arguments.
/*---- Getting Command Line Arguments C Example ("args.c") -------------------*/
/* ANSI C Headers */
#include
#include
/* Main Program starts here */
int main( int argc, char *argv[] )
{
int i;
/* End of declarations ... */
for ( i = 0; i < argc; i++ ) {
printf("Argument %d = \"%s\"\n", i, argv[i] );
}
exit(EXIT_SUCCESS);
}
In order to make it work, you must either run it like this (assuming you are in the directory containing the image)
$ MC SYS$DISK:[]ARGS HELLO WORLDor define a symbol, and invoke it as a "foreign command"
$ args:==$SYS$DISK:[]ARGS
$ args hello world again
Programming Challenge 10
________________________
Try the args program. With your new-found skills, modify Neill's
calc program to take a command line argument expression, or to behave
in the existing manner if one is not supplied.
On VMS systems, we often want to access keyed indexed files. This is slightly more difficult than using the standard file functions, because you have to set up the RMS (Record Management Structures) yourself. If you want to do this, you should really read the DEC C documentation about using RMS from C. Alternatively you can ask your friendly local Clift for an example. Here's one we prepared before the course, "key.c"
/*---- Keyed Index File C Demonstration Program ("key.c") --------------------*/
/*
History:
Version Name Date
V01-001 Neill Clift 09-Mar-1995
Initial version
*/
/* ANSI Headers */
#include
#include
#include
#include
#include
/* VMS Headers */
#include /* RMS RAB */
#include /* RMS access blocks etc */
#include /* System service completion codes */
#include
#include
/* Defines and Macros */
#define NAME_SIZE 32 /* Size of person field */
#define PHONE_SIZE 20 /* Size of the phone number field */
/* Structure declarations */
struct phone_r {
/* Define the structure that will be the record of the keyed file. */
/* It is indexed with two keys for each of the structures fields. */
char name[NAME_SIZE];
char phone[PHONE_SIZE];
};
/* Global Variables - not externally visible */
static struct FAB fab; /* FAB for file */
static struct RAB rab; /* RAB for file */
static struct NAM nam; /* NAM block to report I/O errors nicely */
static struct XABKEY xabkey1, xabkey2; /* XAB to define keys structure */
static char essbuf[NAM$C_MAXRSS]; /* Expanded file name */
static char rssbuf[NAM$C_MAXRSS]; /* Resultant file name */
static char keyname1[32] = "Person"; /* Name of first key */
static char keyname2[32] = "Phone"; /* Name of second key */
/*---- Routine to close the RMS file -----------------------------------------*/
int close_file( void )
{
long int status;
/* End of declarations ... */
status = sys$close( &fab );
return( status );
}
/*---- Open/Create the keyed index file --------------------------------------*/
int create_file( char *filename )
{
long int status, status1;
/* End of declarations ... */
fab = cc$rms_fab; /* initialise the FAB */
fab.fab$l_alq = 100; /* Preallocate space */
fab.fab$w_deq = 100;
fab.fab$b_fac = FAB$M_PUT|FAB$M_DEL|FAB$M_UPD;
fab.fab$l_fop = FAB$M_DFW|FAB$M_CIF;
fab.fab$b_org = FAB$C_IDX;
fab.fab$b_rfm = FAB$C_VAR;
fab.fab$l_fna = filename;
fab.fab$b_fns = strlen(filename);
fab.fab$b_rat = FAB$M_CR;
fab.fab$l_xab = (char *) &xabkey1;
/* Init XABKEY to define key for name key */
xabkey1 = cc$rms_xabkey; /* Initialise XABKEY structure */
xabkey1.xab$b_bln = XAB$C_KEYLEN;
xabkey1.xab$b_cod = XAB$C_KEY;
xabkey1.xab$b_dtp = XAB$C_STG;
xabkey1.xab$b_ref = 0; /* Key zero */
xabkey1.xab$l_knm = (char *) &keyname1; /* Key name */
xabkey1.xab$l_nxt = (char *) &xabkey2; /* Next XAB in chain */
/*
The next two fields describe the section of the record that contain the
key.
*/
xabkey1.xab$w_pos0 = offsetof(struct phone_r, name);
xabkey1.xab$b_siz0 = NAME_SIZE;
/* Init XABKEY to define key for phone kek */
xabkey2 = cc$rms_xabkey; /* Initialise XABKEY structure */
xabkey2.xab$b_bln = XAB$C_KEYLEN;
xabkey2.xab$b_cod = XAB$C_KEY;
xabkey2.xab$b_dtp = XAB$C_STG;
xabkey2.xab$b_ref = 1; /* Key one */
xabkey2.xab$l_knm = (char *) &keyname2;
xabkey2.xab$l_nxt = 0;
xabkey2.xab$w_pos0 = offsetof(struct phone_r, phone);
xabkey2.xab$b_siz0 = PHONE_SIZE;
/*
Init NAM block just for good file I/O error reporting. We won't use it
thought!
*/
nam = cc$rms_nam;
fab.fab$l_nam = &nam;
nam.nam$b_rss = sizeof( rssbuf );
nam.nam$l_rsa = (char *) &rssbuf;
nam.nam$b_ess = sizeof( essbuf );
nam.nam$l_esa = (char *) &essbuf;
status = sys$create( &fab );
if (!(status&SS$_NORMAL))
{
return( status );
}
rab = cc$rms_rab; /* initialise the RAB */
rab.rab$b_mbf = 127;
rab.rab$b_mbc = 127;
rab.rab$l_rop = RAB$M_WBH|RAB$M_RAH;;
rab.rab$l_fab = &fab;
status1 = sys$connect( &rab );
if (!(status1&SS$_NORMAL))
{
status = status1;
sys$close( &fab );
}
return( status );
}
/*---- Write a record to the file --------------------------------------------*/
int put_record( char *name, char *phone )
{
long int status;
struct phone_r phonerec;
/* End of declarations ... */
strncpy( phonerec.name, name, sizeof(phonerec.name) );
strncpy( phonerec.phone, phone, sizeof(phonerec.phone) );
rab.rab$w_rsz = sizeof( phonerec );
rab.rab$l_rbf = (char *) &phonerec;
rab.rab$b_rac = RAB$C_KEY;
rab.rab$l_rop |= RAB$M_UIF;
status = sys$put( &rab );
return( status );
}
/*---- Look a record up by name ----------------------------------------------*/
int get_record( char *name, struct phone_r *phonerec )
{
long int status;
/* End of declarations ... */
rab.rab$w_usz = sizeof( *phonerec );
rab.rab$l_ubf = (char *) phonerec;
rab.rab$b_ksz = strlen( name );
rab.rab$l_kbf = (char *) name;
rab.rab$b_krf = 0;
rab.rab$b_rac = RAB$C_KEY;
rab.rab$l_rop |= RAB$M_UIF;
status = sys$get( &rab );
return( status );
}
/*---- Main Program starts here ----------------------------------------------*/
int main( int argc, char *argv[] )
{
long int status;
struct phone_r phn;
/* End of declarations ... */
printf("Creating phone.dat ...\n");
status = create_file("phone.dat");
if (!(status&SS$_NORMAL)) lib$signal( status );
printf("Add record NEILL - 555 555 1417\n");
status = put_record ("NEILL", "555 555 1417");
if (!(status&SS$_NORMAL)) lib$signal( status );
printf("Add record PHIL - 555 555 6506\n");
status = put_record ("PHIL", "555 555 6506");
if (!(status&SS$_NORMAL)) lib$signal( status );
printf("Look record for PHIL\n");
status = get_record ("PHIL", &phn);
if (!(status&SS$_NORMAL))
{
lib$signal( status );
}
else
{
printf("Found %s - %s\n", phn.name, phn.phone );
}
status = close_file();
if (!(status&SS$_NORMAL)) lib$signal (status);
exit(EXIT_SUCCESS);
}
This will write out a data file, PHONE.DAT, then do a lookup and find a record keyed on the name. Try expanding the file with a few more records, and experiment with the lookup. Use this file to create your own database, with different types of keys

Post a Comment