CSCI 241 - Homework 9:
Writing a Shell

AKA: She sells C-shells down by the C-core

Due by 11am Wednesday, May 10, 2017


For this assignment you will be creating your own version of a shell. It will be focusing on single line command processing and support many of the features common to existing shells.

Your shell's main() function should loop, reading commands from the user and then using fork() and exec(), carry out those instructions. Your program should sensibly handle most errors (print message and loop again).

You are welcome to "Google", etc. for example code and look through the manual pages for various ideas on how to implement the requested functionality, however I want you to credit your sources in the comments and write up the code on your own.

You may work with a partner on this project.


What follows is a list of 10 types of functionality I'd like you to include in your shell. Each is worth 5 points, and there is some extra credit opportunities. I've included references to the C functions and Unix system calls that I think will help you implement that step. If you find that you are stuck working out a particular piece of functionality, you can "fake" that step and demonstrate that the rest is working. (E.g., if you can't parse the command line, you could manually create a sequence of pre-parsed instructions that demonstrate that other functionality is working.)

We will be supporting input/output redirection from/to files, background execution, and pipelines. E.g.,

    foo > output
    foo < input
    foo > output < input
    foo | bar
    foo & 

You should take advantage of the PATH variable that is inherited from the environment.

  1. Inside a loop in your main(), present a prompt to the user and then read a full line of input (up to and including the newline character). Continue looping until EOF is reached.

  2. Run a single word command line (e.g., "ls") by forking a subprocess and execing it. You may assume the word is at the start of the line and that there is no extraneous whitespace. Your main loop should wait until the process completes before continuing.

    [fork(2), execvp(2), waitpid(2)]
  3. Create the following as builtin commands:

    [chdir(2), getenv(3), getpid(2), getppid(2)]
  4. Parse a command line with whitespace into a vector or strings. So something like the string

        "foo |    bar > baz"

    will be turned into

        args[0] = "foo";
        args[1] = "|";
        args[2] = "bar"
        args[3] = ">";
        args[4] = "baz";
        args[5] = NULL;

    I'm told this is likely to be the trickiest part of the assignment.

    For extra credit, handle parsing a command line without spaces between the symbols:  > < & |

    [strtok(3), strchr(3), strsep(3), strpbrk(3)]

  5. Take a vectorized multi-token command line and run the program specified, waiting until the program completes before displaying the next command prompt. I recommend creating a function that will do the actual program execution and return the child PID.

    [fork(2), execvp(2), waitpid(2)]
  6. Trap SIGINT and have it terminate the currently running child process of the shell (if any) and not the shell itself.

    [signal(3), kill(3)]
  7. If the last token on the command line is &, have that program run in the background instead of the foreground. The prompt should be redisplayed without waiting for the child to return. At the start of the loop, before displaying the prompt, check to see if any children need to be reaped, reap them, and display a message to the user about that fact.

    [waitpid(2) with a pid of -1 and the WNOHANG option]

    For extra credit, add in support to trap SIGTSTP (ctrl-z) and have it change the child program that is currently running into a background process. (This might be trickier than I think it is.)

  8. Support for < and > for input and output redirection to the program. Do this by opening the requested file for read/write as needed and duping it to STDIN_FILENO or STDOUT_FILENO.

    [open(2) or fopen(3)/fileno(3), dup2(2)]
  9. Support for command line piping with a single pipe. For example

        foo | bar
    should have STDIN_FILENO of bar be reading from the STDOUT_FILENO of bar. You can do this by using pipe(2) to create connected pairs of file descriptors and then use dup2(2) to set them up appropriately in the children. Close the unused ends (e.g., foo should close the descriptor that bar is using to read). Your shell will need to exec both processes and wait for both of them to return. Note that you can change the "&" in your argument vector to a NULL and just pass in different starting locations of the same vector to your execution function. You may want to have a separate array that keeps track of the start index of various command segments.

    [dup2(2), pipe(2)]

  10. Create a Makefile and README

Extra Credit

  1. Add support for setting and modifying environment variables
  2. Support a multi-pipe sequence.

Programming Notes

Error handling

The following are possible errors that your program should be able to handle without your shell crashing. The first two should be readily handled, but for the last couple you might have some processes that run anyway depending on how you execute portions of the pipeline.

  1. Command not found on the path
  2. Input redirected in from a non-existant file
  3. Input redirected in to a pipe command that is not the first command
  4. Output redirected from a pipe command that is not the last command



Create a file called README that contains

  1. Your name and your partner's name (if any)
  2. A description of what portions of the shell you were able to fully complete, which ones are incomplete, etc.
  3. Any known bugs or errors in your shell
  4. Any interesting design decisions you'd like to share

Now you should clean up your folder (remove test case detritus, object files, etc.) and handin your folder containing your source code and README.

% cd ~/cs241
% handin -c 241 -a 9 hw9

% lshand

Grading Rubric

	looping, forking, parsing  [/18]
		looping     [ /3]
		commands    [ /5]
		exit        [ /1]
		myinfo      [ /1]
		cd          [ /1]
		cd <dir>    [ /2]
		forking     [ /5]
		parsing     [ /5]
    	multi, SIGINT, background  [/15]
    		multi       [ /5]
    		SIGINT	    [ /5]
    		background  [ /5]
	redirection, piping, multi-piping [/10]
    		redirection [ /5]
    		piping      [ /5]

    	Makefile, README, valgrind  [/5]
    		valgrind    [ /2]
    		Makefile    [ /2]
    		README      [ /1]

    	error handling  [/2]

    	extras  [/0]
    		no spaces   		[/2]
    		background  		[/2]
	    	multi-pipe  		[/5]
		environment variables 	[0/2]
    TOTAL  [/50]

Last Modified: May 1, 2017- Roberto Hoyle, based on work by Ben Kuperman