PRACTICE

Now that you've learned about reading and writing files, let's practice with these sample programs:

Practice program 1

Write a program that counts the lines in the files listed on the command line. (This is very similar to the Linux wc -l command.)

Let's keep this simple, and read only one character at a time. We can write a function that reads a file, and counts the number of newlines. It doesn't matter how long or how short each line actually is; for each newline, we know we've reached the end of another line. We can count the newlines to count the number of lines.

#include <stdio.h>

int linecount(FILE *fileptr);

int
main(int argc, char **argv)
{
   int i;
   int nlines;
   FILE *fileptr;

   /* scan each file on the command line */

   for (i = 1; i < argc; i = i + 1) {
      fileptr = fopen(argv[i], "r");

      if (fileptr == NULL) {
         fprintf(stderr, "cannot open file for reading: %s\n", argv[i]);
      }
      else {
         nlines = linecount(fileptr);
         printf("%s: %d lines\n", argv[i], nlines);
         fclose(fileptr);
      }
   }

   /* if no files on the command line, scan stdin instead */

   if (argc == 1) {
      nlines = linecount(stdin);
      printf("stdin: %d lines\n", nlines);
   }

   return 0;
}

int
linecount(FILE *fileptr)
{
   /* read the file, and return the number of newlines in the file */

   int ch;
   int count = 0;

   while ((ch = fgetc(fileptr)) != EOF) {
      if (ch == '\n') {
         count = count + 1;
      }
   }

   return count;
}

I've been careful to write each function at the top of the file, to make reading the sample code easier to read. But remember that you can also write the functions after the main function. When you do that, don't forget to include the function declaration at the top, or you'll get a warning from the C compiler.

Practice program 2

Write a program to read the files listed on the command line, and print their contents to standard output. After every 24 lines, prompt the user with "“Press Enter for more”" (This is basically the same as the FreeDOS MORE command.)

The file might contain lines longer than 80 columns, which will wrap on a standard 80×25 display. That will throw off your line count, but we can ignore that in this simple example. You also don't need to worry about tabs in the input. However, a proper MORE program should account for these.

Let's keep this one simple, as well. We'll write a program that's similar to the TYPE command, but we'll show a prompt after every 24 lines. To make my example program easier to read, I'll also write a separate function to wait for the Enter key.

#include <stdio.h>

#define BUFFER_SIZE 200

void
getenter(void)
{
   int ch;

   /* loop until we find a newline */

   /* there's a better way to do this, but we'll learn about that
      when we learn about conio */

   do {
      ch = getchar();
   } while (ch != '\n');
}

int
more(FILE *fileptr, int maxlines)
{
   /* display a file one "screenful" at a time */

   int ch;
   int lines = 0;

   while ((ch = fgetc(fileptr)) != EOF) {
      putchar(ch);

      /* check if this is a newline. count lines */

      if (ch == '\n') {
         lines = lines + 1;

         /* check if this is the maximum number of lines before a prompt */

         if (lines == maxlines) {
            fprintf(stderr, "<Press Enter for more>");  /* no newline here */
            getenter();

            /* reset the lines counter */

            lines = 0;
         }                             /* if lines == maxlines */
      }                                /* if ch == newline */
   }                                   /* while ch = fgetc */

   /* print a prompt at the end, useful if there's another file */

   fprintf(stderr, "<Press Enter to end>");     /* no newline here */

   /* loop until we find a newline */

   getenter();

   return lines;
}

int
main(int argc, char **argv)
{
   int i;
   FILE *fileptr;

   /* check command line */

   if (argc == 1) {
      fprintf(stderr, "No files given\n");
      fprintf(stderr, "Usage: %s files..\n", argv[0]);
      return 1;
   }

   /* read each file and process */

   for (i = 1; i < argc; i = i + 1) {
      fileptr = fopen(argv[i], "r");

      if (fileptr == NULL) {
         fprintf(stderr, "Cannot open file: %s\n", argv[i]);
      }
      else {
         more(fileptr, 24);
         fclose(fileptr);
      }
   }

   return 0;
}

Practice program 3

Update the above MORE program by reading the files into buffers at a time.

Updating the program to use buffers requires a little more work. But the extra work will be worth it if anyone runs this MORE program to view files over a network or from a floppy disk.

#include <stdio.h>

#define BUFFER_SIZE 200

void
getenter(void)
{
   int ch;

   /* loop until we find a newline */

   /* there's a better way to do this, but we'll learn about that
      when we learn about conio */

   do {
      ch = getchar();
   } while (ch != '\n');
}

int
more_buf(FILE *fileptr, int maxlines)
{
   /* display a file one "screenful" at a time */

   int ch;
   int lines = 0;
   char buffer[BUFFER_SIZE];
   int buffer_length;

   while (!feof(fileptr)) {
      buffer_length = fread(buffer, sizeof(char), BUFFER_SIZE, fileptr);

      /* loop through the buffer */

      for (ch = 0; ch < buffer_length; ch = ch + 1) {
         putchar(buffer[ch]);

         /* check if this is a newline. count lines */

         if (buffer[ch] == '\n') {
            lines = lines + 1;

            /* check if this is the maximum number of lines before a prompt */

            if (lines == maxlines) {
               fprintf(stderr, "<Press Enter for more>");       /* no newline here */
               getenter();

               /* reset the lines counter */

               lines = 0;
            }                          /* if lines == maxlines */
         }                             /* if ch == newline */
      }                                /* for ch */
   }                                   /* while !feof */

   /* print a prompt at the end, useful if there's another file */

   fprintf(stderr, "<Press Enter to end>");     /* no newline here */

   /* loop until we find a newline */

   getenter();

   return lines;
}

int
main(int argc, char **argv)
{
   int i;
   FILE *fileptr;

   /* check command line */

   if (argc == 1) {
      fprintf(stderr, "No files given\n");
      fprintf(stderr, "Usage: %s files..\n", argv[0]);
      return 1;
   }

   /* read each file and process */

   for (i = 1; i < argc; i = i + 1) {
      fileptr = fopen(argv[i], "r");

      if (fileptr == NULL) {
         fprintf(stderr, "Cannot open file: %s\n", argv[i]);
      }
      else {
         more_buf(fileptr, 24);
         fclose(fileptr);
      }
   }

   return 0;
}

Practice program 4

Write a program that takes two single-letter arguments. Write to standard output what you read from standard input—except when you see the first letter, replace it with the second letter instead. (This is very similar to the Linux tr command.)

We could write this with buffers—but as a sample program, it's easier to demonstrate by reading single characters at a time. This is basically the TYPE command that reads a single character at a time. Except in this version, whenever we read a certain character, we'll print a different character.

#include <stdio.h>

int
trch(FILE *in, FILE *out, char from, char to)
{
   int ch;
   int nchanges = 0;                   /* not really needed, but let's count it anyway */

   while ((ch = fgetc(in)) != EOF) {
      if (ch == from) {
         fputc(to, out);
         nchanges = nchanges + 1;
      }
      else {
         fputc(ch, out);
      }
   }

   /* return number of times we changed the char from 'from' to 'to' */

   return nchanges;
}

int
main(int argc, char **argv)
{
   char from, to;

   /* check command line */

   if (argc != 3) {
      fprintf(stderr, "Incorrect command line.\n");
      fprintf(stderr, "Usage: %s c1 c2\n", argv[0]);
      return 1;
   }

   /* get from and to characters */

   from = argv[1][0];
   to = argv[2][0];

   /* use the trch() function to do the work */

   trch(stdin, stdout, from, to);

   /* done */

   return 0;
}