11/8: Baby Shell
For assignment 5 you will build a reasonably functional shell (command line interpreter). Today, we'll get some practice with the system calls you'll need by building a simple shell.
You'll find today's code in cs61-exercises/shell2x
.
sh.c
We have provided a lot of the code necessary to build a shell, including
code to read command lines and break them up into an argc/argv
structure and code to implement a few builtin functions
(ch, chdir, exit
). This means that you get to focus your attention on
writing the code to support creating and executing new processes.
Let's begin by compiling the program as is (make
generates the
executable babysh
). You can ignore the warning about an unused
function; you'll rectify that soon enough. Currently, all the shell can
do is execute a few builtin commands: cd, chdir, exit
. Ideally by the
end of class you'll be able to execute arbitrary programs.
Now examine the code and search for the the string "TODO". Those are the places you need to write code.
You are welcome to peruse the entire code base at your leisure, but you
shouldn't need to modify anything except the dowait
and run_cmd
functions. The overall flow of the program is that is starts in
(surprise) main
, which determines if the shell is running
interactively (i.e., you are going to type commands at the prompt) or
whether we just want babysh
to execute the command specified with the
-c
option on the command line. Assuming interactive mode, we the
interactive
function implements a read-eval-print
loop which calls
getcwd
to read a line of input, and then calls docommand
to break
that input line into arguments, and then either executes a builtin
command or calls run_cmd
, which is the code that you will write to
execute a command by creating a new process.
1. Add Fork/Execvp
Search for TODO. Around line 54, you'll find the place where you need to
add fork
and exec
handling. Go for it! For now, just focus on
getting the process you want running and passing the correct arguments
to it.
Once that's working, move on to the next part.
2. Waiting for your Children
If all you did was add fork
and exec
calls, then you'll find that
the output of the child gets mixed up with the shell prompt. You can
clean that up by calling dowait
at the appropriate time and
implementing that function correctly. Do that next.
3. Embellishments
Once you have a shell that properly waits for its children, you have free reign to implement any number of features. Try your hand at any of the following (many/all of which are part of the next problem set).
- Run jobs in the background: this means that the parent does not wait on its children, but runs in parallel with its children. However, when the child exits, the parent should harvest its status, and report it.
- Use
pipe
anddup2
to allow handling of command pipelines.
- Add environment handling.
- Add any other feature you want
Wrapping Up
Assignment 5 asks you to complete the implementation of a shell that is more advanced than what we did here. However, you have all the basics now to get started. If you wish to reuse any code from today's exercises, be sure to cite it appropriately in your README.
Please take a few moments to fill out this survey.