Contents
C I/O
Program input
There are 3 main sources of input for programs:
- from the command line
you get access to data on the command line by using argc and argv[][]
from standard input (also called stdin)
stdin can be the keyboard, a data file, or the output of another program
- 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.
- 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.
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
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:
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
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.
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:
the UNIX way is to use command line arguments
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