C I/O

Program input

There are 3 main sources of input for programs:

  1. from the command line
    • you get access to data on the command line by using argc and argv[][]

  2. from standard input (also called stdin)

    • stdin can be the keyboard, a data file, or the output of another program

  3. from an 'internally-defined' file
    • open a file, use fscanf(), and don't forget to close the file

1. command line

To read from the command line:

Here is a program that counts from 1 to num, where num is provided by the user on the command line

   1 // countc.c
   2 // reads an integer from the command line and counts
   3 #include <stdio.h>
   4 #include <stdlib.h>
   5 
   6 int main(int argc, char *argv[]) {
   7    int num = 0;
   8    if (argc < 2 || sscanf(argv[1], "%d", &num) != 1) { // num is defined here
   9       fprintf(stderr,"Usage: %s integer\n", argv[0]);
  10       return EXIT_FAILURE;
  11    }
  12    for (int i=1; i<=num; i++) {
  13       printf("%d ", i);
  14    }
  15    printf("\n");
  16    return EXIT_SUCCESS;
  17 }

Notice the program prints a 'Usage' message if an integer argument is missing (discussed in next session)

To execute the program:

prompt$ dcc -o countc countc.c

prompt$ ./count
Usage: ./countc integer

prompt$ ./countc !t#q
Usage: ./countc integer

prompt$ ./countc 10
1 2 3 4 5 6 7 8 9 10

2. standard input

To read from standard input (usually called simply stdin)

   1 //counts.c
   2 // reads an integer from stdin and counts
   3 #include <stdio.h>
   4 #include <stdlib.h>
   5 
   6 int main(void) {
   7    int num;
   8    if (scanf("%d", &num) != 1) {
   9       fprintf(stderr, "Usage: a number expected\n");
  10       return EXIT_FAILURE;
  11    }
  12    // the rest of the program is exactly the same as the command-line version
  13    for (int i=1; i<=num; i++) {
  14       printf("%d ",i);
  15    }
  16    printf("\n");
  17    return EXIT_SUCCESS;
  18 }

Notice the Usage message this time is simpler than the command-line version above

There are many ways to 'test' a program that reads stdin.

  1. Using the keyboard
     prompt$ dcc -o counts counts.c
     prompt$ ./counts
     10
     1 2 3 4 5 6 7 8 9 10

    where the integer 10 was typed on the keyboard by the user, and the program generates the count from 1 to 10.

  2. Using a data file, input.txt say, which contains the integer 10 (followed by a newline).

     prompt$ more input.txt
     10
    
     prompt$ ./counts < input.txt
     1 2 3 4 5 6 7 8 9 10
  3. Using a pipe command. A pipe command joins the stdout of a program to the stdin of another program. If we have a program called write10.c:

       1  // write10.c
       2  // just print the string 10
       3  #include <stdio.h>
       4  #include <stdlib.h>
       5 
       6  int main(void) {
       7     printf("10\n");
       8     return EXIT_SUCCESS;
       9  }
    

    then we can pipe its stdout to the stdin of our counting program

     prompt$ dcc -o write10 write10.c
     prompt$ dcc -o counts counts.c
     prompt$ ./write10 | ./counts
     1 2 3 4 5 6 7 8 9 10

    But you can actually generate a string much more easily in UNIX using echo

     prompt$ echo "10" | ./counts
     1 2 3 4 5 6 7 8 9 10

Some people prefer to use getchar() to read from stdin

   1 // echostdin.c
   2 
   3 #include <stdio.h>
   4 #include <stdlib.h>
   5 int main(int argc, char* argv[]) {
   6   char c = getchar(); // get a char from stdin
   7   while (c != '\n') {
   8       printf("%c", c);
   9       c = getchar();
  10   }
  11   putchar('\n');
  12   return EXIT_SUCCESS;
  13 }

prompt$ dcc echostdin.c
prompt$ echo bornfree | ./a.out
bornfree
prompt$ ./a.out
bornfree
bornfree
prompt%

where the first 'born free' the user typed in, and the second is the echo.

User prompting

You can still use a 'user prompt' when you use stdin but it messes up the output.

   1 // counts+.c
   2 // reads an integer from stdin and counts
   3 // prompts the user
   4 #include <stdio.h>
   5 #include <stdlib.h>
   6 
   7 int main(void) {
   8    int num;
   9    printf("Please input a number: "); // this line added to counts.c
  10    if (scanf("%d", &num) != 1) {
  11       fprintf(stderr, "Usage: a number expected\n");
  12       return EXIT_FAILURE;
  13    }
  14    for (int i=1; i<=num; i++) {
  15       printf("%d ",i);
  16    }
  17    printf("\n");
  18    return EXIT_SUCCESS;
  19 }

results in

prompt$ ./counts+
Please input a number: 10
1 2 3 4 5 6 7 8 9 10

where the program prints the user prompt, the user types in 10, and the program then outputs the count to 10.

If you instead use a pipe as input, then you do not see what the input is

prompt$ echo "10" | ./counts+
Please input a number: 1 2 3 4 5 6 7 8 9 10

You see here that the 10 generated by the echo does not appear on the screen: you just see the output of the program

User prompts are not used often in UNIX because:

  1. the UNIX way is to use command line arguments

  2. it doesn't fit well into stdin/stdout framework (as we saw above)

3. a user file

A program can open and close, and read from, and write to, a file that is defined by the user

This is generally done when you have

These don't happen often. Nevertheless, for the sake of completeness, here is a program that

   1 // files.c
   2 // read a number 'num' from a file input.txt
   3 // write a count from 1 to 'num' to the file OUT
   4 
   5 #define IN  "input.txt"
   6 #define OUT "output.txt"
   7 
   8 #include <stdio.h>
   9 #include <stdlib.h>
  10 
  11 #define NUMDIG 6 // size of numerical strings that are output
  12 
  13 int main(void) {
  14    FILE *fpi, *fpo; // these are file pointers
  15    char s[NUMDIG];
  16 
  17    fpi = fopen(IN, "r");
  18    if (fpi == NULL) { // an important check
  19        fprintf(stderr, "Can't open %s\n", IN);
  20        return EXIT_FAILURE;
  21    }
  22    else {
  23        int num;
  24        if (fscanf(fpi, "%d", &num) != 1) { // an important check
  25            fprintf(stderr, "No number found in %s\n", IN);
  26            return EXIT_FAILURE;
  27        }
  28        else {
  29            fclose(fpi); // don't need the input file anymore
  30            fpo = fopen(OUT, "w");
  31            if (fpo == NULL) { // an important check
  32                fprintf(stderr, "Can't create %s!\n", OUT);
  33                return EXIT_FAILURE;
  34            }
  35            else { // got input and got an output file
  36                fprintf(fpo, "%s", "Counts\n");
  37                for (int i=1; i<=num; i++) {
  38                    sprintf(s, "%d", i);
  39                    fprintf(fpo, "%s\n", s);
  40                }
  41                fclose(fpo);
  42                printf("file %s created\n", OUT);
  43                return EXIT_SUCCESS;
  44            }
  45        }
  46    }
  47 }

Notice:

If you create a data file input.txt that contains the string 13, then you compile and execute the program

prompt$ dcc files.c
prompt$ ./a.out
file output.txt created
prompt$ more output.txt
Counts
1
2
3
4
5
6
7
8
9
10
11
12
13

If the input text file does not exist:

prompt$ ./a.out
Can't open input.txt

and it is for the user to figure out what that means :(

File I/O requires care in programming

You need to have a good reason to use files instead of using stdin/stdout

Program output

There are two standard output 'streams'

Both are normally defined to be the screen

The general form for a print statement is

   1 fprintf(stream, ...)

where 'stream' can be stdout, stderr or a user-defined file. Note

Note the 'systematic' naming:

Like stdin, we can re-direct stdout to a file. For example:

dcc -o counts counts.c
./counts > output.txt
10

(where the integer 10 is input by the user) will result in the count from 1 to 10 going to the file output.txt

If you create a data file input.txt that contains the string 10, then the following will generate the same output text file

./counts < input.txt > output.txt

As we saw before, you can let echo generate data and use that in a pipe. This also generates the same output text file.

echo "10" | ./counts > output.txt

Input/output: in summary

The vast majority of programs can be written just using these library I/O calls

  • scanf() to read from stdin

  • sscanf() to read from the command line

  • printf() to write to stdout

  • fprintf() to write to stderr

Testing can be controlled by shell scripts that execute programs with stdin coming from the script itself or data files

Lec01IO (last edited 2019-06-17 17:54:09 by AlbertNymeyer)