In this lab, you will implement a simple “shell” program. A shell is an interactive program that allows you to start and manage other programs, serving as a sort of intermediary between you, the OS, and the running processes. When you run a program in the terminal, you are actually interacting with the shell.
The shell you write for this program (called
mysh) will support basic functionality that allows you to run programs in the foreground and background, and to move around the filesystem.
The starter code for this lab is available on GitHub at https://github.com/csc213/shell. To start out, fork this repository and add your lab partner as a collaborator.
As you work through this lab, make sure you understand and handle all of the error conditions for the POSIX functions you call. Testing some error conditions is very difficult, so this will mostly depend on a careful reading of the relevant manpages.
Group information is no longer available for this course.
lsin my shell it thinks I am passing the parameter
"". What do I do?
NULLat the end of your args array, not
;, wait for it to finish before moving on. If it ends in
&, move on right away. The examples in the lab using the
sleepcommand are the easiest way to test your implementation that I could come up with, but if you have alterantives I would be happy to see them.
The starter code includes a simple command prompt loop that reads in a line of text and prints it back out.
The first step in actually executing these commands is to break them into pieces.
Take a look at the manpage for
execvp, which is the variant of
exec we will use for this lab.
This function takes in a command name or path and an array of arguments than ends with a
Write code to break the command line string into an array of
char*s ending with a
You can do this with repeated calls to
You may assume there will be no more than 128 parts to any one command. Please use the
MAX_ARGS constant defined in
mysh.c so this number can be changed without replacing constants sprinkled throughout your code.
You do not need to support quoted arguments in your shell.
Now that you can read in and break apart commands, use
fork to create a child process,
execvp to launch the command in the child process, and
wait to have the shell wait for the child process to complete.
The reason we are using the
execvp function for this lab is so we do not need to implement path resolution, the process of searching through directories in the
PATH environment variable to find an executable that matches the given command name.
The convention for
exec is that the first argument passed to the program is the name of the program.
For example, the command
ls /home/curtsinger runs the program
/bin/ls with the arguments
When the child process completes, print the exit code for the child process before showing the command prompt again. For example:
> ls Makefile README.md common.mk mysh mysh.c obj Child process 12802 exited with status 0 > grep usage: grep [-abcDEFGHhIiJLlmnOoqRSsUVvwxZ] [-A num] [-B num] [-C[num]] [-e pattern] [-f file] [--binary-files=value] [--color=when] [--context[=num]] [--directories=action] [--label] [--line-buffered] [--null] [pattern] [file ...] Child process 12804 exited with status 2
Hint: Pay close attention to the meaning of the parameter you pass to
wait. This value is not the same as the child process’ exit status!
While the command
cd is actually an executable in
/bin, this won’t work for our shell;
when you run the command you do change directories, but this doesn’t change the working directory of the parent process (your shell).
Instead, your shell will need to check if the current command is
If it is, your shell should call the
chdir function to change directories instead of calling
exec to run
/bin/cd in a child process.
You should also add special handling for blank lines (which should do nothing) and the
Most shells also allow you to invoke multiple commands in sequence using a semicolon.
cd ..; pwd; ls will move up a directory, print the full path of that directory, then print the files in that directory.
Modify your shell to support multiple commands chained together with a semicolon.
Hint: You will need to revisit your command parsing code from part A to break a command line string into commands separated by semicolons. The code in the
parser directory may be useful for this step, and the next step as well.
Add support for background commands, which are launched with the
The most common use case for background commands is to launch a single executable without blocking the current shell.
For example, the command
sleep 5 will block the shell for 5 seconds before a prompt is printed again, while
sleep 5 & will run the
sleep command in the background and allow you to run additional commands immediately.
It is also possible to run multiple commands with a single invocation separated by an ampersand, much like a semicolon. Unlike the semicolon separator, the ampersand will run the joined commands simultaneously instead of in sequence. You can also combine semicolon and ampersand delimited commands, with the following rules.
Hint: Don’t pass the ampersand or semicolon separator as an argument to the child process or built-in command.
Hint: These rules imply the semicolons and ampersands have the same precedence. Simply associate them with the command that precedes them.
Hint: There is code in the
parser directory of the starter code that may be helpful for breaking commands containing semicolons and ampersands into their subcommands.
When you have one or more background commands running, the shell should print the exit status of any command that quits between command invocations. For example:
> sleep 1 & sleep 1; sleep 1 (shell pauses for 1 second here) Child process 12805 exited with status 0 Child process 12806 exited with status 0 (shell pauses for 1 second here) Child process 12807 exited with status 0 >
You can check for this behavior with a simple test in a real shell, as well as your
Type the command
sleep 1 & and hit enter.
Then, sit on the blank command prompt for well over one second.
You should not see any indication that the one second sleep has finished.
Then, hit enter to run a blank command.
Before the next prompt appears, you should see the exit status of the sleep command.
You can use the
wait function to block until a child process quits (a semicolon command), but you will need to use
waitpid with the
WNOHANG flag to check for any completed background processes.
With this flag, the function will return immediately and indicate whether there are any additional zombie processes to collect.
Do this repeatedly until there are no more zombies, then move on to the next command or prompt for a new one.
Another important feature of a shell is the ability to compose commands to perform more complex operations. You will receive 2% extra credit for each the following features:
`) to use the output of that command as part of another command line.
<, or the output of a command to a file with
If you implement any of these features, include a note in your pull request to submit the lab saying which features you have implemented.
To implement these features, you may need the functions
These use some file-related functionality that we haven’t talked about in class.
I am happy to discuss these with you during office hours.