Reading from stdin
In this recipe, we'll learn how to write a program in C that reads from standard input. Doing so enables your programs to take input from other programs via a pipe, making them easier to use as a filter, thus making them more useful in the long run.
Getting ready
You'll need the GCC compiler and preferably the Bash shell for this recipe, although it should work with any shell.
To fully understand the program that we are about to write, you should look at an ASCII table, an example of which can be found at the following URL: https://github.com/PacktPublishing/Linux-System-Programming-Techniques/blob/master/ch2/ascii-table.md.
How to do it…
In this recipe, we will write a program that takes single words as input, converts their cases (uppercase into lower and lowercase into upper), and prints the result to standard output. Let's get started:
- Write the following code into a file and save it as
case-changer.c
. In this program, we usefgets()
to read characters from stdin. We then use afor
loop to loop over the input, character by character. Before we start the next loop with the next line of input, we must zero out the arrays usingmemset()
:#include <stdio.h> #include <string.h> int main(void) {     char c[20] = { 0 };     char newcase[20] = { 0 };     int i;     while(fgets(c, sizeof(c), stdin) != NULL)     {         for(i=0; i<=sizeof(c); i++)         {             /* Upper case to lower case */             if ( (c[i] >= 65) && (c[i] <= 90) )             {                 newcase[i] = c[i] + 32;             }             /* Lower case to upper case */             if ( (c[i] >= 97 && c[i] <= 122) )             {                 newcase[i] = c[i] - 32;             }         }         printf("%s\n", newcase);         /* zero out the arrays so there are no            left-overs in the next run */         memset(c, 0, sizeof(c));         memset(newcase, 0, sizeof(newcase));     }     return 0; }
- Compile the program:
$> gcc case-changer.c -o case-changer
- Try it out by typing some words in it. Quit the program by pressing Ctrl + D:
$> ./case-changer hello HELLO AbCdEf aBcDeF
- Now, try to pipe some input to it, for example, the first five lines from
ls
:$> ls / | head -n 5 | ./case-changer BIN BOOT DEV ETC HOME
- Let's try to pipe some uppercase words into it from a manual page:
$> man ls | egrep '^[A-Z]+$' | ./case-changer name synopsis description author copyrigh
How it works…
First, we created two character arrays of 20 bytes each and initialize them to 0.
Then, we used fgets()
, wrapped in a while
loop, to read characters from standard input. The fgets()
function reads characters until it reaches a newline character or an End Of File (EOF). The characters that are read are stored in the c
array, and also returned.
To read more input—that is, more than one word—we continue reading input with the help of the while
loop. The while
loop won't finish until we either press Ctrl + D or the input stream is empty.
The fgets()
function returns the character read on success and NULL
on error or when an EOF occurs while no characters have been read (that is, no more input). Let's break down the fgets()
function so that we can understand it better:
fgets(c, sizeof(c), stdin)
The first argument, c
, is where we store the data. In this case, it's our character array.
The second argument, sizeof(c)
, is the maximum size we want to read. The fgets()
function is safe here; it reads one less than the size we specify. In our case, it will only read 19 characters, leaving room for the null character.
The final and third argument, stdin
, is the stream we want to read from—in our case, standard input.
Inside the while
loop is where the case conversions are happening, character by character in the for
loop. In the first if
statement, we check if the current character is an uppercase one. If it is, then we add 32 to the character. For example, if the character is A, then it's represented by 65 in the ASCII table. When we add 32, we get 97, which is a. The same goes for the entire alphabet. It's always 32 characters apart between the uppercase and lowercase versions.
The next if
statement does the reverse. If the character is a lowercase one, we subtract 32 and get the uppercase version.
Since we are only checking characters between 65 and 90, and 97 and 122, all other characters are ignored.
Once we printed the result on the screen, we reset the character arrays to all zeros with memset()
. If we don't do this, we will have leftover characters in the next run.
Using the program
We tried the program by running it interactively and typing words into it. Each time we hit the Enter key, the word is transformed; the uppercase letters will become lowercase and vice versa.
Then, we piped data to it from the ls
command. That output got converted into uppercase letters.
Then, we tried to pipe it uppercase words from the manual page (the headings). All the headings in a manual page are uppercase and start at the beginning of the line. This is what we "grep" for with egrep
, and then pipe to our case-changer
program.
There's more…
For more information about fgets()
, see the manual page, man 3 fgets
.
You can write a small program to print a minimum ASCII table for the letters a-z and A-Z. This small program also demonstrates that each character is represented by a number:
ascii-table.c
#include <stdio.h> int main(void) {     char c;     for (c = 65; c<=90; c++)     {         printf("%c = %d    ", c, c); /* upper case */         printf("%c = %d\n", c+32, c+32); /* lower case */     }     return 0; }