C代写:ICS53 Shell

实现Linux的Shell工具,需要完成Background Process, Redirection, Piping和Signals的功能。



The goal of this assignment is to become familiar with low-level Unix/POSIX system calls related to processes, file access, and interprocess communication (pipes and redirection). You will write a basic shell which supports a few basic operations.


After completing this homework assignment, you should:

  • Understand process execution, pipes and forks
  • Have an advanced understanding of Unix commands and the command line
  • Have gained experience with C libraries and system calls
  • Have enhanced your C programming abilities

Hints and Tips

  • Check the return codes of all system calls. This will help you catch errors. Refer to the lecture notes on writing error handling wrappers for conceptually how to handle this. Note, however your shell should not exit on error as is shown in the examples.
  • Make sure to handle any errors! Throw lots of junk commands into your shell to test.
  • Your shell should NEVER crash. We will be deducting points for each time your program crashes during grading. Make sure that your code handles invalid input gracefully. For this assignment, we will be grading your submissions by hand. That means that as far as error messages are concerned, you only need to make sure that all of your output is reasonable and human readable.

Getting Started

Download the base code for this assignment: hw4_basefiles.tar.
The structure is:

├── include 
│   ├── helpers.h // All add functions that you want to declare go here 
│   └── shell.h   // or here 
├── Makefile  
└── src 
    ├── helpers.c // All helper functions that you want to define go here, or in 
    └── shell.c   // the base code for this assignment 

shell.c file provides you a very simple shell that supports the following basic functionalities:

  • Prints a simple command prompt and waits for user input: <netid>$
  • Reads in a string (command) from the user (User must press enter)
  • Tokenizes the entered command into a set of strings (tokens) based on whitespace characters
    • The base code supports a command with at most 15 tokens.
    • The provided tokenizer will treat all characters between double-quotes as a single token.
  • Attempts to run the entered command as a foreground process using execvp()
  • The shell will terminate if the user types exit.

All code and implementation must be placed in the shell.h/.c and helpers.h/.c files. Do not create any additional C files.

Restrictions & Important Information

The provided base code will compile and run a simple shell program. It demonstrates the basic concept of a shell program. However, it lacks some important features:

  • You cannot change directories in your shell. Changing directories is a shell built-in command (EC option).
  • Executables may be stored anywhere on your computer. For example, the program cat may be located in /bin/cat while grep may be located in /usr/bin/grep. You never have to specifically type /bin/cat though. The reason for this is that your shell will automatically search common paths for programs. As such your shell can only run programs which are located within the specified PATH directory or the current directory (where your shell program was run from).
  • In general, you may use almost any standard library function or system call to complete this homework. There is one important exception: you may not use the system(3) function. This function is effectively a wrapper for a shell and using it would defeat the purpose of the whole assignment. Speaking more broadly, any library function or system call that is effectively a wrapper for another shell is prohibited in this assignment. Using any of these functions will result in an automatic zero for the assignment.

Shell Implementation Requirements

In this homework, the goal is to add features to the base shell program to make it more useful and practical. Below are the requirements:

  • Support for a foreground and single background process simultaneously
  • Support for I/O redirection (stdin, stdout, stderr) and piping between two programs
  • Catching and handling signals

Before you begin, replace <netid> in the shell prompt with your actual netid.

Background Process

A background process executes without blocking the shell from taking additional commands. This means the program is runs and the shell does not wait for it to finish before returning to the user with a new prompt. This is accomplished by spawning a child process to execute the command/program and not waiting for the child to terminate. The standard operator to specify the command to be run in as a background process is &. Only when this operator is added to the command as the very last token will the shell recognize it as an indicator for background processes.
An example:

<ics53>$ ./longRunningProgram arg1 arg2 arg3 &

Please note that for this homework, you only need to support ONE background process at a time. If another background process is entered by the user while the first is running, simply reject the second background process and continue with the previous process. To track if a background process is running record the pid of the child process and set it to -1 when there is no background process running.

When the background process terminates, the shell must make sure the terminated background process is reaped. There are two ways to handle this: (a) using waitpid() or (b) creating a signal handler for SIGCHLD. For (a) when calling waitpid(), you need to add the option WNOHANG to ensure the function does not block waiting for a child to terminate. Refer to man waitpid(2) for more details.

Determining when to check if the background process has terminated is an important design decision in this assignment.

For (b), you will need to consider that case where a foreground and background process are both running.

SIGCHLD could occur for either process and your shell should act accordingly to reap/clean up either/both processes.

All test cases will have at least one whitespace character/newline character on either side of the & sign.


The shell must support the following Unix I/O redirection options: >, 2>, >>, &>, and <. Consult with this webpage and/or here to review I/O redirection usage. We summarize additionally here.

One of the most powerful features of a Unix shell is the ability to compose a series of simple applications to create a complex workflow. The feature that enables this composition is output redirection. Basic redirection is accomplished by three special characters: <, >, and | (explained in next section).

The < operator indicates that you are to take standard input from the specified file:

<ics53>$ cat < input.txt

The input.txt file must exist and be openable. If not, an error occurs.
The > operator indicates that you are to write standard output to the specified file:

<ics53>$ echo "This text will go in the file." > output.txt

The output.txt file is created or overwritten if it exists.

Next we have the 2> operator. While the > operator redirects standard output to a file, 2> will redirect the output of stderr to a file. For instance, in the following example, if the executable printToStderr prints text to standard error, then it will be redirected to the file output.txt.

<ics53>$ ./printToStderr 2> output.txt

The output.txt file is created or overwritten if it exists.

A special case of the operator just discussed is &>. This operator will redirect both standard output and standard error to the same file, output.txt. The file is created or overwritten if it exists.

The >> operator works similar to the > operator in that it also redirects standard output to the specified file. However, it will append the output to the end of the specified file, if it exists. If the file does not exist, it is created. For example:

<ics53>$ echo "This will overwrite the file" > output.txt
<ics53>$ echo "This will append to the file" >> output.txt

The result is that output.txt should have the following contents:

This will overwrite the file
This will append to the file

For all of the examples above, you can run them in your VM bash terminal to observe the expected functionality.
In order to implement this functionality in your shell, you will need to do the following items:

  • open/create the required input or output files
  • fork() and exec() a child process, and
  • use dup() or dup2() (see man dup) to make copies and set the proper file descriptors

It is important to determine which process (the parent or child) should do each of these items, the order in which to do them, and which file descriptors and when/who should be closed.

Redirection operators always appear after all arguments to the program. This means, the set of redirection operators and associated file arguments will appear after all program arguments, but before the ‘&’ for background process.

Remember, your code is now the shell. Therefore, when the shell tokenizes the user’s command the redirection symbols will be in the token list. It is your job to detect them and to handle their operation correctly. Hint: You should not pass these tokens to the program.

Your shell will be tested with single redirection operator (one of >, 2>, >>, &>, and <), as well as combinations of valid redirection together. Valid combinations include:

  • < along with one of the output options (>, 2>, >>, &>)
  • < along with > and 2>
  • > and 2> together with no <
  • >> and 2> together with no <

Redirection WILL NOT be tested in conjunction with piping (presented in the next section).
All test cases will have at least one whitespace character on either side of any of the redirection operators.


Your shell must also support piping. For this assignment, you will support only a single pipe.

Additionally, for simplicity, your program does not need to support a pipe along with any redirection operators in a single command. If you are new to the concept of piping start with this link.

The pipe operator, |, connects the standard output of the first program to the standard input of the next program. For example,

<ics53>$ echo "this is sent to stdin of the next program" | grep the

the echo program will print the string to standard out which is then fed to the grep program on standard in.

To accomplish this task you will need to use pipe()/pipe2() (see man pipe). The basic steps, using the above example, are as follows:

  • create a single pipe to connect echo to grep
  • fork() two times, one for each command/program (echo and grep)
  • the child for echo must connect its stdout file descriptor to the write end of the pipe
  • the child for grep must connect its stdin file descriptor to the read end of the pipe
  • the each child runs their command

All test cases will have at least one whitespace character on either side of the | .


Normally, exceptional control flow occurs in the operating system kernel. We saw this when we studied exceptions. Signals are a way for your program to hook into and handle/block some of these exceptions.

Review section 8.5 in your textbook before attempting this section of the assignment. Additionally,

Implement the following signal operations:

  1. Block the SIGTSTP signal. When a user tries to stop your shell by using Ctrl-Z, your shell should do nothing at all.
  2. Handle the SIGUSR2 signal. The signal SIGUSR2 is a user defined signal and can be interpreted however we want. When this signal is sent to your process, you should print to standard out, “Hi User!\n”.

To test your handling of SIGUSR2 by opening a new vssh connection to your VM and execute the following command where [pid] is the process ID of your currently running shell:

<ics53>$ kill -s SIGUSR2 [pid]

Extra Credit

For this assignment we have included a number of Extra Credit options:

  • Add the ability to change directories with the built-in cd command. Support cd dir and cd .. must be supported. See man chdir(2)
  • Support a combination of redirection and piping in a single command. Note, that not all combinations are valid combination, and error detection for these cases should be performed.
  • Support multiple (3+) pipes in a single command.
  • Support multiple background processes at one time, as well as provide a built in command list, which will display on stdout the pid and command running for each background process.
  • Implement the command, fg [pid], which will bring a background process back to the foreground. More information about this command can be found (here).
  • Add additional information to your command prompt such as username, machine, date/time, and current directory path. Consider changing the color of the prompt (but not the commands).


Make sure your directory tree looks like this and that your homework compiles. Tar and upload the following directory structure as hw4_netid.tar to the Canvas assignment prior to the deadline.

├── include 
│   ├── helpers.h // All add functions that you want to declare go here 
│   └── shell.h   // or here 
├── Makefile  
└── src 
    ├── helpers.c // All helper functions that you want to define go here, or in 
    └── shell.c   // the base code for this assignment 

Make sure that your submission compiles by downloading your tar file, extracting it in a new folder and compiling and running your program. If you program does not run you will get a 0!

NOTE: When writing your program try to comment as much as possible. Try to stay consistent with your formatting too! This will make it easier to debug your program!