The goal is to build a shell interpreter combining behavior from common shells like Bash and csh. Some skeleton code is provided.
- Part 1: Monday, September 23 11:59pm
- Part 2: Wednesday, October 2 11:59pm
- Part 3: Friday, October 11 11:59pm All extra credit parts are due with the final submission.
Much of the shell will be graded using automatic testing. To run tests:
./testall partN(where N = 1, 2, or 3) runs tests for a specific part../testallwith no arguments runs all tests.
- Login and Build:
- Log in to a CS department machine (lab machine or
data.cs.purdue.edu), navigate to a preferred directory, and rungit clone ~cs252/repos/$USER/proj3.gitandcd proj3. - Build the shell by typing
makeand start it by typing./shell.
- Log in to a CS department machine (lab machine or
- Parsing and Executing Commands
- Scanner and Parser:
- Use Lex and Yacc to write a scanner and parser for the shell. The
command.hfile implements a data structure representing a shell command. Thestruct single_commandrepresents an argument list for a single command, and thestruct commandrepresents a list of simple commands (which can be just onesingle_command). It also has fields for input, output, and error redirection. - Look through the
Makefileto understand how the program is built. C source code files are generated fromshell.landshell.yand then compiled.
- Use Lex and Yacc to write a scanner and parser for the shell. The
- Accepting more complex commands:
- Modify
shell.landshell.yto support the grammar:cmd [arg]* [ | cmd [arg]* ]* [ [> filename] [< filename] [2> filename] [ >& filename] [>> filename] [>>& filename] ]* [&] - Test with commands like
ls,ls -al,ls -al aaa bbb cc,ls -al aaa bbb cc > outfile, etc.
- Modify
- Scanner and Parser:
- Executing Commands
- Single command process creation and execution:
- For each single command, create a new process using
fork()and callexecvp()to execute the executable. If the command is not in the background, the shell waits for the last single command to finish usingwaitpid(). Refer to the man pages for function details. The filels_grep.cis an example of process creation and redirection. - After this, commands like
ls -alandls -al /etc &should be executable.
- For each single command, create a new process using
- File redirection:
- If the command specifies IO redirection files, create them as necessary. Use
dup2()to change file descriptors (0 for standard input, 1 for output, 2 for error). - Examples:
ls -al > out,cat -q cat 2> dog, etc. - Notes:
2>redirects stderr to the specified file.>&redirects both stdout and stderr to the specified file.>>appends stdout to the specified file.>>&appends both stdout and stderr to the specified file.
- If the command specifies IO redirection files, create them as necessary. Use
- Pipes:
- Use
pipe()to create a pipe for inter-process communication. Redirect the output of one command to the input of the next usingdup2(). See the example inls_grep.c. - Commands like
ls -al | grep commandandls -al | grep command | grep command.o > outshould work.
- Use
- isatty():
- When the shell uses a file as standard input, it should not print a prompt. Use
isatty()to determine if the input is from a file or terminal. This is required for the automated tests to pass.
- When the shell uses a file as standard input, it should not print a prompt. Use
- Exit:
- Implement a special command
exitthat exits the shell when run. It should be parsed by the shell and cause the shell to exit without creating a new process.
- Implement a special command
- Single command process creation and execution:
- Submission
- By Monday, September 23 11:59pm:
- Log in to a CS department machine.
- Navigate to the
proj3directory. - Run
make clean. - Run
maketo check if the shell builds correctly. - Run
make submit_part1.
- Standard rules apply:
- Follow the coding standards on the course website.
- The shell must compile and run on lab Linux machines.
- Do not look at others' source code or work with other students.
- By Monday, September 23 11:59pm:
- ctrl-C
- Make the shell behave like bash with respect to
ctrl-C. Whenctrl-Cis pressed while a command is running, it should interrupt and usually terminate the command. Ifctrl-Cis typed when no command is running, the current prompt is discarded and a fresh prompt is printed. Seecrtlc.cfor an example of detecting and ignoring theSIGINTsignal and thesigaction()man page.
- Make the shell behave like bash with respect to
- Zombie Elimination
- Set up a signal handler to catch the
SIGCHLDsignals sent when a child process finishes. The signal handler should callwaitpid()to clean up zombie child processes. The shell should also print the process ID of the child when a background process exits in the form “[PID] exited.”
- Set up a signal handler to catch the
- Quotes
- Add support for quotes in the shell. Arguments with spaces surrounded by quotes should be treated as a single argument. For example,
myshell> ls "command.cc Makefile"should listcommand.ccandMakefileif they exist. The quotes should be removed before using the argument.
- Add support for quotes in the shell. Arguments with spaces surrounded by quotes should be treated as a single argument. For example,
- Escaping
- Allow the escape character. Any character after
\can be part of an argument, including special characters like quotation marks and ampersands. For example,myshell> echo \"Hello between quotes\"should print"Hello between quotes", andmyshell> echo this is an ampersand \& this is an ampersand &should print the correct text with the ampersand.
- Allow the escape character. Any character after
- Builtin Functions
- Implement the following builtin commands:
printenv: Prints the environment variables of the shell. The environment variables are stored inchar **environ, a null-terminated array of strings. Refer to theenvironman page.setenv A B: Sets the environment variableAto valueB.unsetenv A: Un-sets environment variableA.source A: Runs fileAline-by-line as if it were typed into the shell by a user.cd A: Changes the current directory toA. If no directory is specified, defaults to the home directory. Refer to thechdir()man page.
- These built-ins should be used like other commands, including with redirection and piping.
- Implement the following builtin commands:
- “.shellrc”
- When the shell starts, it should attempt to run “source.shellrc” equivalent.
- Submission
- By Wednesday, October 2 11:59pm:
- Log in to a CS department machine.
- Navigate to the
proj3directory. - Run
make clean. - Run
maketo check if the shell builds correctly. - Run
make submit_part2.
- Standard rules apply as in Part 1.
- By Wednesday, October 2 11:59pm:
- Subshells
- Implement subshells. Any argument of the form $(command and args) will be processed by a child shell process, and the output will be fed back into the original parent shell. For example,
echo $(expr 1 + 1)becomesecho 2, andecho a b > dir; ls $(cat dir)lists the contents of directoriesaandb. - Steps:
- Extract the command between ”$(“ and ”)” in
shell.l. - Invoke the shell as a child process and provide the extracted command as input using two pipes to communicate (one to write the command to the child and one to read the output from the child).
- Place the output obtained from the subshell into the scanner’s buffer using
yy_unput(int c)in reverse order. Do not usepopen()or a temporary file for interprocess communication.
- Extract the command between ”$(“ and ”)” in
- Implement subshells. Any argument of the form $(command and args) will be processed by a child shell process, and the output will be fed back into the original parent shell. For example,
- Environment variable expansion
- Implement environment variable expansion. When a string of the form
${var}appears in an argument, it will be expanded to the value corresponding to the variablevarin the environment table. Also implement special expansions like${$}(PID of the shell process),${?}(return code of the last executed simple command),${!}(PID of the last background process),${!!}(run the last command again),${_}(last argument in the previous command excluding redirects),${SHELL}(path of the shell executable, can userealpath()to expand the relative path).
- Implement environment variable expansion. When a string of the form
- Tilde expansion
- When
~appears by itself or before$"/"it will be expanded to the home directory of the current user. If~appears before a word, the characters after~up to the first$"/"will be expanded to the home directory of the user with that login. Tilde expansion should not happen within double quotes.
- When
- Wildcarding
- Implement wildcarding as follows:
- First, handle wildcarding only within the current directory:
- Before inserting a new argument in the current simple command, check if it contains a wildcard character (* or?). If so, insert the file names that match the wildcard (include their absolute paths).
- Use
opendirandreaddirto get all entries of the current directory. Useregcompandregexecto find the entries that match the wildcard (convert from wildcards to regular expressions as needed, seeregular.ccexample). - Commands like
echo *,echo *.c,echo shell.?should work.
- Then make it work for any path (examples:
echo */*,echo */*/*). Do not use theglob()call.
- First, handle wildcarding only within the current directory:
- Implement wildcarding as follows:
- Edit mode
tty_raw_mode.ccontains sample code to change the terminal’s input from canonical to raw mode. Implement a full line editor inread_line.c.- Add the following code to
shell.lafter the#includelines:%{ #include <string.h> #include "y.tab.h" //////////// Start added code /////////// extern "C" char *read_line(); int mygetc(FILE *f) { static char *p; char ch; if (!isatty(0)) { // stdin is not a tty. Call real getc return getc(f); } // stdin is a tty. Call our read_line. if ((p == NULL) || (*p == 0)) { p = s; char *s = read_line(); } ch = *p; p++; return ch; } #undef getc #define getc(f) mygetc(f) //////////// End added code /////////// %} %% - Modify the
Makefileby definingEDIT_MODE_ON(e.g.,EDIT_MODE_ON=yes) to compile the shell with the line editor. - In
read_line.c, add the following editor commands:- Left arrow key: Move the cursor to the left and allow insertion. Do nothing if at the beginning of the line.
- Right arrow key: Move the cursor to the right and allow insertion. Do nothing if at the end of the line.
- Delete key (ctrl-D): Remove the character at the cursor, shift characters on the right to the left.
- Backspace key (ctrl-H): Remove the character before the cursor, shift characters on the right to the left.
- Home key (ctrl-A): Move the cursor to the beginning of the line.
- End key (ctrl-E): Move the cursor to the end of the line.
- Do not use the
readlinelibrary. Implement your own line editor.
- History
- Implement a history list. Every time the user runs a new command, a row is added to the table. Implement the following editor commands:
- Up arrow key: Shows the previous command in the history list.
- Down arrow key: Shows the next command in the history list.
- Implement a history list. Every time the user runs a new command, a row is added to the table. Implement the following editor commands:
- ctrl-R
- Implement the
ctrl-Rfeature like in Bash. Pressingctrl-Rallows the user to search through the command history by typing part of a command. Pressing “Enter” executes the matched command. Pressingctrl-Ragain shows other matches.
- Implement the
- Submission
- By Friday, October 11 11:59pm:
- Add a
READMEto theproj3/directory with:- Features that work as specified in the handout.
- Features that do not work as specified in the handout.
- Extra features implemented.
- Log in to a CS department machine.
- Navigate to the
proj3directory. - Run
make clean. - Run
maketo check if the shell builds correctly. - Run
make submit_part3.
- Add a
- Standard rules apply as in Part 1.
- By Friday, October 11 11:59pm:
- Process Substitution (8 points)
- Implement process substitution. Use
mkdtemp()to generate a temporary directory for a named pipe. Create a named pipe withmkfifo()within the temp directory. Connect the substituted command’s output to the named pipe and pass the pipe name to the other command. Clean up resources (fifo and directory) after the command is executed usingunlink()andrmdir()functions.
- Implement process substitution. Use
- Path Completion (2 points)
- Implement path completion. When the key is typed, the editor attempts to expand the current word to a matching file similar to Bash.
- Aliases (2 points)
- Implement aliases. Allow users to define commands that encompass other commands (e.g., create shortcuts or add default arguments). For example, an alias for
lsto always colorize the output, list entries by columns, and append an indicator.
- Implement aliases. Allow users to define commands that encompass other commands (e.g., create shortcuts or add default arguments). For example, an alias for
- Jobs (8 points)
- Implement job control features like in Bash:
jobs: Display the status of jobs in the current session (refer to thejobsman page for operands).fg: Run jobs in the foreground (refer to thefgman page for operands).bg: Run jobs in the background (refer to thebgman page for operands).ctrl-Z: Suspend a foreground process by sending it aSIGTSTPsignal (implement a similar signal handler as forctrl-C).
- Implement job control features like in Bash:
- Rough breakdown:
- Part 1: 30 points (tested with
./testall part1) - Part 2: 30 points (tested with
./testall part2) - Part 3: 30 points (tested with
./testall part3) - Grading of
readlineandctrl-C: 10 points
- Part 1: 30 points (tested with
- Penalties:
- -5 points for memory leaks.
- -5 points for file descriptor leaks. The parts build on each other, and points are allocated from previous parts in subsequent parts.