Processes (proc) Project
Overview
In this project, you will develop a simple version of a command-line shell that can invoke programs and perform redirection and piping.
Input
USAGE: ./myshThe program should be called mysh. It takes no
arguments. When running, it reads a command (or commands) from stdin. It
executes the command(s) and then exits.
mysh should support up to one pipe between two commands
as well as redirection in and out (including both simultaneously) for
any command.
Grammar
start ::= pipes "\n"
pipes ::= command pipe*
pipe ::= "|" command
command ::= program in? out?
program ::= STRING STRING*
in ::= "<" STRING
out ::= ">" STRING
This grammar allows for any number of pipes, but only one pipe (or no pipe) is required to be supported. Supporting unbounded pipes is a bonus feature.
This grammar requires that a redirect in comes before a redirect out,
i.e., ls <infile >outfile is allowed, but
ls >outfile <infile is not. This makes parsing the
command input simpler.
Redirection
Redirection works by prefixing (with no additional space!) the name
of a file with < to redirect standard input and/or
> to redirect standard output. (There is no need to
support stderr.) There should only be one redirection of each kind at
most. The redirection will be after arguments.
Piping
Piping works by putting a pipe symbol between two commands. Only a single pipe is required to be supported. It is an error if redirection of standard out is used with piping of the left command or in redirection of standard in is used with piping of the left command.
Example
Redirection
ls>out.txtNotice the lack of a space between > and
out.txt. The lexer will pick out these tokens for you
regardless of whitespace.
Piping
ls / | wcNotice that the programs can still take arguments even with a pipe (or redirect).
Piping and redirection
cat < file.txt | wc > wc.outThe commands in pipes can still take redirects. We will only test redirect in for the first program and redirect out for the last command. Bash does permit other redirects, but these end up taking precedence over the pipe and we will not test for this.
Output
The output of command should match the output of running the command with bash, making it easy to create ground truth for testing.
Starter code
This only every needs to be run once.
mkdir ~/cop3402fal2026/proc
cd ~/cop3402fal2026/proc
curl https://www.cs.ucf.edu/~pappas/teaching/cop3402/fal2026/files/proc.tar | tar -xvf -
mv -i mysh.template.c mysh.c # do not overwrite your existing code accidentallyBe sure to include the starter code in your git commit so that graders cloning it will have a buildable program.
Implementation
Lexer
The lexer identifies the individual tokens of the command input and
handles whitespace for you. The lexer is given to you
(lex.l). The lexer is called with yylex() and
returns the type of token, while setting yytext to the
actual text (lexeme) of the token. Below are the names of the tokens as
defined in the token type enum, token_t (found in
tokens.h):
typedef enum {
STRING,
PIPE,
REDIRECT_IN,
REDIRECT_OUT,
END_OF_LINE,
} token_t;The token enum names correspond to the actual text of the tokens as given in the below table. Whitespace is ignored.
| Grammar Symbol (Terminal) | Token enum |
|---|---|
| string | STRING |
| “” | PIPE |
| “<” | REDIRECT_IN |
| “>” | REDIRECT_OUT |
| “” | END_OF_LINE |
The code template provides a consume() function which
will read the next token from the input and save the lexeme (text) of
the token into the global variables lookahead and
lexeme respectively. Use consume() and inspect
lookahead and lexeme to parse the input into a
data structure.
Parser
Write a parser that takes tokens from the user input (via consume() and both checks that the input
is valid (according to the grammar) and creates
an in-memory data structure to store
the commands.
See the lecture notes on parsing for an example to get you started.
You are also free to interpret the commands (run programs, do redirection and piping) during parsing, although this may be more challenging.
The code template provides an empty parse() function for
you to implement your parser in.
The result of parsing should be a linked list of command_t structs. Use the
provided add_command() function (util.h) to
create and add a new, empty command_t struct to the list.
add_command() will returns a pointer to the new struct that
you can then populate with the program name, arguments, and redirect
file names.
Use the provided print_commands() function
(util.h) to help debug your parser.
Command structure
The code template provides you with a data structure,
command_t in util.h, to store the results of
parsing the commands from the input.
typedef struct command_s {
char *argv[MAX_ARGS]; // NULL-terminated list, i.e., the next element after the last should be NULL.
char *in; // NULL if there is no redirect
char *out; // NULL if there is no redirect
struct command_s *next;
} command_t;The data structure has one field for the argv array. This stores
both the name of the program and the arguments, i.e.,
if the command is ls path1 path2, argv will
be
{ "ls", "path1", "path2", NULL }The last element in the argv list must be succeeded by
NULL. This is requirement of the exec series of commands
(man 2 execv, “The array of pointers must be terminated by
a null pointer.).
There are fields for the name of file to redirect stdin from
(in) and the name of the file to redirect standard out to
(out). If redirects are not present for the command in the
user input, these fields should be NULL.
The next field points to the next element in the linked
list or NULL when it is the last element.
add_command()
(util.h) allocates a new command_t struct and
appends it to the list for you.
The beginning of the list is pointed to by the global variable
first_command (util.h) and the end by
last_command (util.h), each of which will be
NULL if the list is empty.
You can check for list sizes with the following conditions:
| Condition | Description |
|---|---|
first_command == last_command == NULL |
Empty list |
first_command == last_command && first_command != NULL |
Single-element list |
first_command != NULL && first-command->next == last_command |
Two-element |
first_command != NULL && first_command != last_command |
More than one element (only needed for bonus) |
You are also free to define your own data structure(s) if you prefer. For the non-bonus project, you only need to support up to two commands from the input.
Executing commands
fork() creates a new
process, dup2() redirects
I/O, and pipe() creates a
pipe.
For program execution, you can use execvp (instead of
execve) which does path lookups for you.
When performing redirection, be sure to close the pipe file descriptors in the parent or the program reading the pipe may hang waiting for the file to close.
You can use the following in the parent to wait until there are no more child processes running:
do {
while (wait(NULL) > 0);
} while (errno != ECHILD);Error checking
syscall errors should always be checked.
Report errors via exit codes passed to the exit library
call, i.e., exit(EXIT_FAILURE).exit code
5.
Error messages are optional, but should only be written to stderr,
not to stdout, which will be used for program output, i.e., use
perror() or fprintf(stderr, ...) instead of
printf.
No erroneous commands will be given for testing.
System call and library references
Do not use helper libraries or other simplified calls to achieve similar results as functions below. Just use the syscalls below for these aspects of the project. The template code has all the header files you will need for the project. If you believe you need other library calls, please double-check with the instructor.
| Symbol | Reference | Reading |
|---|---|---|
| fork() | man 3 fork |
LPI 24 |
| wait() | man 2 wait |
LPI 24 |
| execvp() | man 3 execvp |
LPI 27 |
| pipe() | man 2 pipe |
LPI 44.2 |
| dup2() | man 2 close |
LPI 44.4 |
| open() | man 2 open |
LPI 4.1 |
| close() | man 2 close |
LPI 4.1 |
| perror() | man 3 perror |
LPI 3.4 |
| exit() | man 3 exit |
LPI 25 |
| _exit() | man 2 exit |
LPI 25 |
manis the command-line manual.- LPS is The Linux Program Interface book.
- Knowledge of memory management and string processing is assumed.
Headers to include
#include <stdio.h>
#include <stdbool.h>
#include <malloc.h>
#include <unistd.h> // fork()
#include <stdlib.h> // exit()
#include <inttypes.h> // intmax_t
#include <sys/wait.h> // wait()
#include <string.h> // strcspn(), strtok
#include <errno.h> // ECHILD
#include <fcntl.h> // O_RDONLY, open
#include "tokens.h"
#include "util.h"
#include "lex.c"Note that lex.c isn’t a header, but the generated code
from the lexer.
Building and running the tool
Create a Makefile that will build your project and give the resulting
program the name mysh. See the hello project for an example
Makefile.
Your project /must/ be buildable with make and runnable
with ./mysh, both from the root of your repo, i.e.,
cd ~/cop3402fal2026/proc
make
./myshAutomated grading will build and run your tool this way and only this way.
Again, be sure that you include all files from the project
starter code in your repo so that it builds with
make.
Submitting your project
git setup
Be sure to complete the vc exercise before attempting this project.
Create a new local repository, following the directions in the vc
exercise (including the git --set-upstream
last step) and set the local and remote repository URLs to be the
following locations:
| Local repository | ~/cop3402fal2026/proc |
| Remote repository | gitolite3@eustis3.eecs.ucf.edu:cop3402/$USER/proc |
Commands to setup your repo
These steps only setup the repo, do not submit your code, and assume you have already completed the vc exercise. Consult that exercise for specifics on validating each step and submitting your code.
mkdir -p ~/cop3402fal2026/proc
cd ~/cop3402fal2026/proc
git init
echo "proc project" > README.md
git add README.md
git commit README.md
# Enter a commit message in the editor that pops up
git remote add submission gitolite3@eustis3.eecs.ucf.edu:cop3402/$USER/proc
git push --set-upstream submission masterSelf-check
See the hello project for instructions on cloning a project from the grading server.
Grading
The test cases used to grade the project are listed below. Scripts to run the odd-numbered test cases are provided publicly, while the even-numbered test cases are private.
To use the automated tests, first download them, e.g.,
cd ~/cop3402fal2026/proc
wget https://www.cs.ucf.edu/~pappas/teaching/cop3402/fal2026/files/proc_public_tests.tar
tar -xvf proc_public_tests.tarTo run a test case, e.g., tests/03.sh, use the following
bash command from the root of your local repository directory:
bash proc-tests/03.sh $(realpath mysh)
echo $?This runs the bash script, passing in the absolute path to your
program, which is what $(realpath mysh) finds for you. The
result of echo $? should be 0 which indicates
the test was successful.
To see what commands the test case is running exactly, use
bash -x instead of just bash:
bash -x proc-tests/03.sh $(realpath mysh)
echo $?Test cases
| # | Description | Expected Output |
|---|---|---|
| 1 | Run a program with no args. | It should print to stdout. |
| 2 | Run a program by name without giving the full path and with no args. | It should print to stdout. |
| 3 | Run a program that takes an argument. | It should print to stdout |
| 4 | Run a program, reading its input from stdin. | It should print to stdout. |
| 5 | Run a program, redirecting its output to /dev/null | It should print nothing to stdout. |
| 6 | Run echo. | It should print to stdout. |
| 7 | Run echo, redirecting to a file. | It should print to the file. |
| 8 | Run cat, redirecting both stdin and stdout from and to files. | It should copy the contents of the input file to the output file. |
| 9 | Pipe the output of ls to cat. | It should print the output of ls to stdout. |
| 10 | Pipe a program to word count. | It should print to stdout the word count of the first program’s output. |
| 11 | Pipe ls to wc -l | It should print only the number of lines from ls. |
| 12 | Pipe a program with arguments to another program with arguments. | It should correctly run both programs, piping between them, printing to stdout. |
| 13 | Pipe find to grep. | It should correctly filter the results of find to stdout. |
| 14 | Pipe find to grep, but redirect the output. | It should correctly filter the results of find to the given file. |
| 15 | Pipe cat to grep, redirecting cat’s stdin from a file. | It should correctly filter the contents of the input file to stdout. |
| 16 | Redirect in to one program, pipe to another, redirect output. | The file should contain the correct output. |
| 17 | Same as 05, but alternative spacing around redirect symbol | Same as 05 |
| 18 | Same as 07, but alternative spacing around redirect symbol | Same as 07 |
| 19 | Same as 09, but alternative spacing around pipe symbol | Same as 09 |
| 20 | Same as 12, but alternative spacing around pipe symbol | Same as 12 |
| 21 | Same as 13, but alternative spacing around pipe symbol | Same as 13 |
| 22 | Same as 15, but alternative spacing around pipe and redirect symbols | Same as 15 |
| 23 | Same as 15, but alternative spacing around pipe and redirect symbols | Same as 15 |
| 24 | Same as 16, but alternative spacing around pipe and redirect symbols | Same as 16 |
Bonus feature
Support more than one pipe between executables. The bonus feature test cases will test 2, 3, and more pipes in a single command with and without redirection and with differing spacing between the pipe and redirect symbols.
Grading schema
| Criterion | Points |
|---|---|
| The git repo exists | 1 |
| The program is written, builds, and runs as described here | 1 |
| Tests pass, prorated by the number of test cases passing | 6 |
| ———————————————————— | ——– |
| TOTAL | 8 |