Differences between revisions 46 and 48 (spanning 2 versions)
Revision 46 as of 2019-06-14 04:35:16
Size: 10190
Comment:
Revision 48 as of 2019-06-17 17:52:58
Size: 10663
Comment:
Deletions are marked like this. Additions are marked like this.
Line 136: Line 136:
}}}

Some people prefer to use ''getchar()'' to read from ''stdin''
{{{#!cplusplus
// echostdin.c

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[]) {
  char c = getchar(); // get a char from stdin
  while (c != '\n') {
      printf("%c", c);
      c = getchar();
  }
  putchar('\n');
  return EXIT_SUCCESS;
}
}}}
{{{
prompt$ dcc echostdin.c
prompt$ echo bornfree | ./a.out
bornfree
prompt$ ./a.out
bornfree
bornfree
prompt%

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:

  • include argc and argv in your parameter list for main().

  • use sscanf() to read the arguments (it stands for 'string scanf()')

    • the first argument of a sscanf() is a string

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)

  • a scanf() is used (instead of sscanf())

    • a scanf() misses the string argument of a sscanf()

  • so where does num comes from?

    • ... the default 'channel' 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

  • ... because we did not declare argc and argv, and so cannot use argv[0] this time!!

    • we could have if we wanted to of course

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%

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.

  • that looks fine

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

  • which is sort-of messed up

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

  • large volumes of stored data, or
  • complex data (such as structs) or
  • non-printable data

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

  • reads a number from a file input.txt

  • writes the count from 1 to that number to the file output.txt

    • it is user-friendly :) : it tells the user that an output file has been created

   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:

  • all the error messages go to stderr

  • the 'file is created' message goes to stdout

  • read is done using fscanf(), and write using fprintf()

  • as it is written the user must know that input and output files are used

    • ... could be re-written to prompt the user for the file names

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

  • more housekeeping
  • more difficult to maintain

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

Program output

There are two standard output 'streams'

  • stdout

  • stderr

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

  • the call printf(...) is the same as fprintf(stdout, ...)

  • the 'stream' can be a user-defined file pointer
  • a fprintf(stderr, ...) is usually reserved for serious errors

    • you may ask is a 'Usage' message a 'serious error'?

    • or ask is incorrect input a 'serious error'?

    • but it is clear that
      • a file that cannot be opened is a serious error
      • a string that cannot be read is a serious error

Note the 'systematic' naming:

  • standard input is scanf(),

    • if you read from a string then use sscanf(), where the first argument is the string

  • standard output is printf(),

    • if you write to a file then use fprintf(), where the first argument is a stream

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)