To expand the simple shell of lab 2 to allow pipes and I/O redirection.
To provide additional insights about Unix shells through direct experience with pipes and file descriptors.
This lab draws upon three sources:
Our Lab on a Simple Unix Shell provided a basic framework for reading and executing successive command lines,
Programs fork-2.c through fork-6.c in An Introduction to Concurrency in
Unix-based [GNU] C Through Annotated Examples illustrate several uses
of pipes within C programs, and
Lab Exericses 9.1 and 9.2 in Nutt's text discuss pipes and input/output redirection.
Much of this lab is inspired by Nutt's Lab Exercise 9.2
Lab Exercise 2 for this course asked you to write a simple Unix-style shell according to the following outline:
In this lab, you are to extend the allowed command lines in three ways:
Allow the [first] program on the command line to redirect standard input, so reading is done from a designated file.
Allow pipes to be specified to connect successive programs, running as separate processes.
Allow the [last] program on the line to redirect standard output, so writing is done to a named file.
Within the Unix/Linux command-line environment, programs normally read from "standard in" and write to "standard out". By default, "standard in" is usually the keyboard, and "standard out" is usually a computer monitor at a person's workstation. For example, consider program max-min.c. This program reads an integer n, followed by n real numbers, and finds the maximum, minimum, and average of the real numbers. All reading is done from "standard in" and output is to "standard out". The program also prints out the ith real number. Thus, a typical run might be:
%gcc -o max-min max-min.c && max-min
Program to process real numbers.
Enter number of reals: 7
Enter 7 numbers: 3.0 1.0 4.0 1.0 5.0 9.0 2.0
Maximum: 9.00
Minimum: 1.00
Average: 3.57
Enter the index (1..n) of the number to be printed: 6
The 6 th number is 9.00
In running the program, Unix allows data to be read from a file, rather than from "standard in". For example, suppose that a file "data" contains the following entries, which repeat exactly what the user typed in the above session:
7
3.0 1.0 4.0 1.0 5.0 9.0 2.0
6
Unix allows information to be read from the file "data" rather than from the keyboard, with the following command:
%gcc -o max-min max-min.c && max-min < data
In this command, the less than sign (<) indicates that the name that follows (data) should be used for input rather than the keyboard. The output is exactly as above, except that the user's typing is not seen (it came from the file).
In this example, the output looks a bit strange, as the prompts for data are still printed to the screen, even though the input from the file is not echoed. When redirecting input, it is sometimes advised not to prompt the user, although that is more a matter of form than a technical requirement.
Similarly, all output could be written to a file rather than to a monitor. Thus, to print output in a file called "results", we might use the following command:
%gcc -o max-min max-min.c && max-min > results
In this case, the program will wait for you to enter the relevant data, but no prompts appear on your screen. Rather, all output goes to the file "results".
Redirection of both input and output can be combined on a single Unix command line:
%gcc -o max-min max-min.c && max-min < data > results
In this context, nothing appears on the screen either from the user typing input or the program printing output.
Nutt discusses I/O redirection in Lab Exercise 9.2 using
open and dup.
Alternatively, an implementation of input or output redirection paralleling the use of pipes may follow 3 main steps:
open to set up the file, giving an integer file descriptor
as a result. (Use the parameter O_RDONLY for reading,
or the parameter O_WRONLY | O_CREAT for writing.)
dup2 to copy the file descriptor to
STDIN_FILENO or STDOUT_FILENO.
Program io-redirection.c demonstrates how these steps might be added to the example program, max-min.c.
Within a Unix command line, one can designate the output of one program
as the input of another. Further, this capability can be expanded through
a series of programs. For example, a listing of the directory
/home/walker/c/examples yields:
%ls
core
fork-1
fork-1.c
fork-1.out
fork-2
fork-2.c
fork-2.out
fork-3
fork-3.c
fork-3.out
fork-4
fork-4.c
fork-4.out
fork-5
fork-5.c
fork-5.out
fork-6
fork-6.c
fork-6.out
intro.dvi
intro.pdf
intro.ps
intro.tex
read-write-1
read-write-1.c
read-write-1.out
read-write-1.out-a
read-write-1.out-b
read-write-2
read-write-2.c
read-write-2.out
read-write-3
read-write-3.c
read-write-3.out
read-write-4
read-write-4.c
read-write-4.out
Within this listing, we might be interested in files that end in
.c. Of course, one approach to this result would be to use
the command ls *.c. However, another approach would be
to utilize the filter program grep, lookng for files
that contain the strong .c. In this approach, we want
to generate the full listing, send the output through a pipe to
grep, filter the file names, and print the results:
%ls | grep .c
fork-1.c
fork-2.c
fork-3.c
fork-4.c
fork-5.c
fork-6.c
read-write-1.c
read-write-2.c
read-write-3.c
read-write-4.c
In this command, the vertical line | indicates the
output of program should be sent to the next program.
As an additional step, we might want to filter out all file names
containing the number 3, based on the command grep -v 3.
Adding another pipe to the previous command to include this step yields
%ls | grep .c | grep -v 3
fork-1.c
fork-2.c
fork-4.c
fork-5.c
fork-6.c
read-write-1.c
read-write-2.c
read-write-4.c
Implementation of such pipes follows closely the programs
fork-2.c through fork-6.c in An Introduction to Concurrency in
Unix-based [GNU] C Through Annotated Examples.
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~walker/courses/213.fa04/lab-shell-expanded.shtml
|
created October 11, 2004 last revised October 11, 2004 |
|
| For more information, please contact Henry M. Walker at (walker@cs.grinnell.edu) |