Differences between revisions 5 and 28 (spanning 23 versions)
Revision 5 as of 2019-06-12 12:53:19
Size: 5723
Comment:
Revision 28 as of 2019-06-13 11:16:14
Size: 9589
Comment:
Deletions are marked like this. Additions are marked like this.
Line 51: Line 51:
Usage: ./count integer

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

prompt$ ./count 10
Usage: ./countc integer

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

prompt$ ./countc 10
Line 136: Line 136:
==== User prompting ====
You can still use a 'user prompt' when you use ''stdin'' but it messes up the output.
{{{#!cplusplus
// counts+.c
// reads an integer from stdin and counts
// prompts the user
#include <stdio.h>
#include <stdlib.h>

int main(void) {
   int num;
   printf("Please input a number: "); // this line added to counts.c
   if (scanf("%d", &num) != 1) {
      fprintf(stderr, "Usage: a number expected\n");
      return EXIT_FAILURE;
   }
   for (int i=1; i<=num; i++) {
      printf("%d ",i);
   }
   printf("\n");
   return EXIT_SUCCESS;
}
}}}
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''

{{{#!wiki note
User prompts are not used often in UNIX because:
 1. the ''UNIX way'' is to use command line arguments
 1. it doesn't fit well into ''stdin''/''stdout'' framework (as we saw above)
}}}
Line 141: Line 186:
This is generally done when you have large volumes of stored data, or complex data or non-printable data.
 * these rarely happen in this course
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

But, 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

{{{#!cplusplus
// files.c
// read a number 'num' from a file input.txt
// write a count from 1 to 'num' to the file OUT

#define IN "input.txt"
#define OUT "output.txt"

#include <stdio.h>
#include <stdlib.h>

#define NUMDIG 6

int main(void) {
   FILE *fpi, *fpo; // these are file pointers
   char s[NUMDIG];

   fpi = fopen(IN, "r");
   if (fpi == NULL) { // an important check
       fprintf(stderr, "Can't open %s\n", IN);
       return EXIT_FAILURE;
   }
   else {
       int num;
       if (fscanf(fpi, "%d", &num) != 1) {
           fprintf(stderr, "No number found in %s\n", IN);
           return EXIT_FAILURE;
       }
       else {
           fpo = fopen(OUT, "w");
           if (fpo == NULL) { // an important check
               fprintf(stderr, "Can't create %s!\n", OUT);
               return EXIT_FAILURE;
           }
           else {
               fprintf(fpo, "%s", "Counts\n");
               for (int i=1; i<=num; i++) {
                   sprintf(s, "%d", i);
                   fprintf(fpo, "%s\n", s);
               }
               fclose(fpo);
               printf("file %s created\n", OUT);
               return EXIT_SUCCESS;
           }
       }
   }
}
}}}
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
  * ... but is this all necessary/elegant/useful??

After creating a file ''input.txt'', compiling and executing is as follows:
{{{
prompt$ more input.txt
13
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
}}}
Line 146: Line 276:
There are two standard output 'channels', standard output ''stdout'' and standard error ''stderr''. There are two standard output 'streams', standard output ''stdout'' and standard error ''stderr''.
Line 149: Line 279:
''printf()'' writes to ''stdout'', hence the screen

''fprintf()'', which stands for ''file-printf'', writes to a file, which could be ''stderr'
 * for example, in ''countc.c'' the 'Usage' message went to ''stderr'':
 {{{#!cplusplus
 fprintf(stderr,"Usage: %s integer\n", argv[0]);
 }}}
  * you could argue over whether it should go to ''stderr'' (hence be just a ''printf'')
  * ''stderr'' is normally reserved for __serious errors__
There is convention here:
The general form for a print statement is ''fprintf(stream, ...)'', where stream can be stdout, stderr or a user-defined file.
 * the call ''printf(...)'' is the same as ''fprintf(stdout, ...)'' and is default to the screen but may be re-directed
 * a ''fprintf(stderr, ...)'' is usually reserved for serious errors
  * for example, in ''countc.c'' above the 'Usage' message went to ''stderr'':
  {{{#!cplusplus
  fprintf(stderr,"Usage: %s integer\n", argv[0]);
  }}}
  * you could argue over whether 'bad' usage is a serious error (maybe just a ''printf'' would have been enough)

There is 'systematic' naming here:
Line 160: Line 290:
  * from a string is '''s'''''scanf()'', and it has an 'extra' first argument that is a string   * if you read from a string then use '''s'''''scanf()'', where the first argument is the string
Line 162: Line 292:
  * to a file is '''f'''''printf()'', and it has an 'extra' first argument that is a 'channel' (file)

Like ''stdin'', we can re-direct ''stdout'' to a file
  * if you write to a file then use '''f'''''printf()'', where the first argument is a stream

Like ''stdin'', we can re-direct ''stdout'' to a file.
Line 174: Line 304:
The following does the same thing: If you create a data file ''input.txt'' that contains the string ''10'', then the following will generate the same output text file.
Line 179: Line 309:
This also does the same thing: As we saw before, you can let ''echo'' generate data and use that in a pipe. This also generates the same output text file.
Line 186: Line 316:
The vast majority of program can be written just using these i/o system calls
 - ''scanf()'' to read from ''stdin''
 -
''sscanf()'' to read from the command line
 - ''printf()'' to write to ''stdout''
 - ''fprintf()'' to write to ''stderr''
{{{#!wiki note
The vast majority of program 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''
}}}

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

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 file

A program can open and close, and read from, and write to, a file that is 'internally' defined.

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

But, 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
  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) {
  25            fprintf(stderr, "No number found in %s\n", IN);
  26            return EXIT_FAILURE;
  27        }
  28        else {
  29            fpo = fopen(OUT, "w");
  30            if (fpo == NULL) { // an important check
  31                fprintf(stderr, "Can't create %s!\n", OUT);
  32                return EXIT_FAILURE;
  33            }
  34            else {
  35                fprintf(fpo, "%s", "Counts\n");
  36                for (int i=1; i<=num; i++) {
  37                    sprintf(s, "%d", i);
  38                    fprintf(fpo, "%s\n", s);
  39                }
  40                fclose(fpo);
  41                printf("file %s created\n", OUT);
  42                return EXIT_SUCCESS;
  43            }
  44        }
  45    }
  46 }

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
    • ... but is this all necessary/elegant/useful??

After creating a file input.txt, compiling and executing is as follows:

prompt$ more input.txt
13
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

Program output

There are two standard output 'streams', standard output stdout and standard error stderr.

  • both are normally defined as the screen

The general form for a print statement is fprintf(stream, ...), where stream can be stdout, stderr or a user-defined file.

  • the call printf(...) is the same as fprintf(stdout, ...) and is default to the screen but may be re-directed

  • a fprintf(stderr, ...) is usually reserved for serious errors

    • for example, in countc.c above the 'Usage' message went to stderr:

         1   fprintf(stderr,"Usage: %s integer\n", argv[0]);
      
    • you could argue over whether 'bad' usage is a serious error (maybe just a printf would have been enough)

There is 'systematic' naming here:

  • 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 design considerations

The vast majority of program 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

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