Why this matters
A huge class of useful programs read some input, transform it, and write output: wc, grep, sort, sed, compilers, log analyzers. On Unix they are called filters, and they are designed to be chained with pipes (cat log | grep error | wc -l). A filter cannot know in advance how much input is coming. It reads until the input runs out, a condition called end of file. Getting that loop right is a small piece of code that you will write over and over, and it has two classic traps.
Standard input, redirection and pipes
Every process starts with three open streams: standard input (stdin), standard output (stdout) and standard error (stderr). By default stdin is the keyboard and stdout the terminal, but the shell can connect them elsewhere without the program knowing:
$ ./prog # stdin from the keyboard
$ ./prog < notes.txt # stdin from a file
$ ls -l | ./prog # stdin from another program's output
$ ./prog < in.txt > out.txt # and stdout to a file
Your program reads stdin the same way in every case. "End of file" means: the file has no more bytes, or the program writing into the pipe has finished, or, at the keyboard, the user pressed Ctrl+D at the start of a line. (Ctrl+D is not a character that gets read; it tells the terminal to report end of input.)
getchar, putchar and EOF
getchar() reads one byte from stdin and returns it as an int in the range 0 to 255. When there is nothing more to read, it returns the special value EOF, a macro from <stdio.h> that is -1 on Linux. putchar(c) writes one byte to stdout.
The canonical loop copies input to output:
#include <stdio.h>
int main(void)
{
int c;
while ((c = getchar()) != EOF)
putchar(c);
return 0;
}
The condition does three things in one expression, which is why it looks dense at first:
( c = getchar() ) != EOF
| | |
| 1. read a byte |
2. store it in c |
3. the value of the assignment is the stored value; compare it with EOF
The extra parentheses around the assignment are required: != binds more tightly than =, so c = getchar() != EOF would store the comparison result (0 or 1) in c.
A small change gives a counting program:
int c;
long chars = 0, lines = 0;
while ((c = getchar()) != EOF) {
chars++;
if (c == '\n')
lines++;
}
printf("%ld lines, %ld characters\n", lines, chars);
$ printf 'one two\nthree\n' | ./prog
2 lines, 14 characters
(long and %ld are used so the counts cannot overflow on big inputs.)
What is underneath: buffering
Calling the kernel once per byte would be very slow. Instead, stdin has a buffer inside your process. The first getchar asks the kernel for a large chunk (typically 4096 bytes, or one line at a terminal) with a single read system call; later getchar calls just hand out bytes from the buffer until it is empty. Output is buffered the same way: putchar adds to a buffer that is written out when it fills, at a newline (for a terminal), or when the program exits.
kernel --read(4096 bytes)--> [ stdin buffer: o n e t w o \n t h r e e \n ]
^
getchar() returns the next byte from here
That is why character-at-a-time loops in C are fast, and why at the keyboard your program sees nothing until you press Enter: the terminal delivers a whole line at a time.
Character classification: ctype.h
<ctype.h> provides functions to classify and convert characters. They take an int (a getchar result or EOF) and return nonzero for true:
| function | true for |
|---|---|
isspace(c) | space, \t, \n, \r, \v, \f |
isdigit(c) | 0 to 9 |
isalpha(c) | letters |
isalnum(c) | letters and digits |
isupper(c), islower(c) | upper / lower case letters |
toupper(c), tolower(c) | convert case (other characters unchanged) |
An uppercase filter is three lines:
int c;
while ((c = getchar()) != EOF)
putchar(toupper(c));
$ echo "hello, world" | ./prog
HELLO, WORLD
Remembering where you are: state
Many filters need to remember something about what they have already seen. Counting words is the classic example: a word starts when a non-space character follows a space (or the start of input). One int flag is enough:
non-space: words++ non-space: (nothing)
OUTSIDE -------------------------> INSIDE ------+
^ | | ^ |
| +-- space: (nothing) | +------+
+---------------------------------+
space
This is a tiny state machine: the program is always in one of a few states, and each input character moves it between them. The exercises use this idea twice. State machines scale up to tokenizers, protocol parsers and regular expression engines.
Reading numbers until end of file
For numbers, use scanf and make its return value the loop condition, exactly as in the sentinel loop of the previous lesson:
int x;
long sum = 0;
while (scanf("%d", &x) == 1)
sum += x;
scanf returns the number of items it converted: 1 on success, 0 if the next input is not a number (for example a letter), and EOF at end of input. Testing == 1 stops on both.
Common mistakes
Storing getchar's result in a char. On x86, char is signed, so the byte 0xFF becomes -1, equal to EOF. This copy loop stops early:
char c; /* BUG */
while ((c = getchar()) != EOF)
putchar(c);
$ printf 'ab\xffcd\n' | ./prog | od -c
0000000 a b
The bytes after 0xFF are lost. On platforms where char is unsigned (common on ARM), the loop instead never ends. Always use int.
Looping on feof. feof(stdin) becomes true only after a read has failed, so this loop runs one time too many:
while (!feof(stdin)) { /* BUG */
scanf("%d", &x);
sum += x;
}
With input 10 20 30 it prints sum = 90: the final, failing scanf leaves x at 30, which is added again. Test the read itself.
Forgetting the parentheses: while (c = getchar() != EOF) sets c to 1 for every byte. gcc warns suggest parentheses around assignment used as truth value.
Waiting at the keyboard. If you run a filter with no redirection, it waits for you to type. Press Ctrl+D (on a new line) to send end of file, or Ctrl+C to abort.
Recap
- Filters read
stdinuntil end of file; the shell can connectstdinto a file (<) or a pipe (|) transparently. while ((c = getchar()) != EOF)withint cis the standard byte loop;putcharwrites bytes.stdinandstdoutare buffered, so byte-at-a-time code is still efficient.<ctype.h>classifies and converts characters.- A flag variable turns a loop into a state machine (inside a word, at the start of a line).
- Never store getchar's result in a
char, and never loop onfeof; test the read itself.
Going further: compare your word counter from the exercises with the real thing: printf 'the quick brown\n fox jumps\n' | wc. Read man 3 getchar and man 3 isalpha.