We are not implementing a full, traditional shell.
For example, we won't use process groups, and support full terminal support to run the likes of nano, emacs, vim, or top, nor are we implementing wildcards (*) and strings ("" nor '').
Instead, we'll support the core of a shell: terminal input and output, pipelines, output redirection, and shell control over individual processes.
There are many ways to implement a shell. They are motivated by the requirements of
- Parsing inputs,
- Enabling program composition into pipelines,
- The need for the shell to
wait(orwaitpid) on children, - The interactions between the shell and children via signals (i.e. terminating them),
- Enabling background computation,
- The interactions between the terminal and the shell (keyboard input and
cntl-c/cntl-z), and - The redirection of child output to files.
When the execute logic returns, there are no foreground tasks.
Either they have excited, or the foreground task was put into the background.
This means that either the shell decided to not block waiting for the pipeline, or that the wait calls have returned, indicating that the foreground commands have exited.
The global pipelines data-structure tracks all active pipelines and is used to formulate output of the jobs command, and is accessed using the fg command.
Pipelines can be executed in the foreground or the background^[We will not support suspending commands (using SIGSTOP, and later waking using SIGCONT), and will instead only support tasks running in the foreground and background.].
Foreground pipelines are the default.
A foreground pipeline has three key properties:
- it can be interacted with on the keyboard,
- it prevents the shell prompt from being presented, and
- it can be interacted with using keyboard shortcuts, for example,
cntl-cto terminate the pipeline orcntl-zto put the pipeline into the background.
The core of how these are enabled is that the shell waits (or waitpids) for the foreground processes, thus enabling them to interact with the keyboard.
Background tasks are still executing (unlike in traditional shells that might suspend tasks with cntl-z), but can only be interacted with using a few builtin commands.
jobswhich lists all of the background pipelines, andfg Nin which pipelineNis brought to the foreground.
As background tasks don't require terminal input, there can be multiple/many executing at any point in time.
A pipeline that terminates with a & is run in the background immediately.
exit- builtin command to exit.- an empty input must exit the shell (this is necessary for the automatic tests)
The string inputs on the command line include the following operators.
See msh.h for the description of the referenced errors.
;- the sequence operator separates pipelines. The pipeline preceding the;must finish execution before the pipeline after is executed. Empty pipelines before or after the;are allowed (indicating "there is no command"). The last pipeline is the one after the last;. When the last pipeline in a sequence finishes (all its commands haveexited), the user has the ability to type more inputs. Errors includeMSH_ERR_SEQ_REDIR_OR_BACKGROUND_MISSING_CMD.|- the pipe operator to redirect output from the previous command to the input of the next. Errors includeMSH_ERR_PIPE_MISSING_CMD,MSH_ERR_TOO_MANY_CMDS.&- the background operator is the last non-space character in a pipeline, and indicates that this pipeline should be run in the background. Pipelines run in the background enable the next pipeline in the sequence to execute before the previous one that executes in the background is finished, or if the background pipeline is the last, the shell returns to take additional user input. Errors includeMSH_ERR_MISUSED_BACKGROUND.1>,1>>,2>, and2>>- are redirection operators requesting that the standard output, file descriptor1, should be output to a file, or that the standard error, file descriptor2, should be output a file. The file name must follow these operators, and redirection operators must be last in a command aside from the optional&(they must follow the program and the arguments). The>variants will delete the specified file before outputting into it, while the>>variants will append to the file. In either case, if the file does not already exist, it will be created. Redirections of the standard output stream are only allowed Errors includeMSH_ERR_MULT_REDIRECTIONS,MSH_ERR_REDIRECTED_TO_TOO_MANY_FILES, andMSH_ERR_NO_REDIR_FILE.
Each operator must be surrounded by spaces unless at the end of the input. Each pipeline is a set of commands that includes a program to execute, and optionally:
- a set of arguments to that program, and then, optionally,
- redirection operators with their associated files.
Potential errors include MSH_ERR_TOO_MANY_ARGS, MSH_ERR_NO_EXEC_PROG.
These are defined in msh.h.
Make sure to use the macros, not the constant values (i.e. use MSH_MAXARGS, not 16 in your code).
Given these assumptions, you might make your struct msh_pipeline hold a fixed array of MSH_MAXCMNDS struct msh_commands.
As such, these assumptions are meant to simply your allocations, should you choose to use internal array allocations.
the tests are in tests/.
They are explained further in the specific milestone.
The mshparse/ directory holds the files necessary, all of which will be built into a library.
You should not modify the mshparse/msh_parse.h nor msh.h files.
This milestone will focus on parsing the shell's input. The strings entered as input into the shell require pulling them apart into
- different pipelines,
- the commands in the pipelines, and
- the commands and arguments.
You'll see in mshparse/msh_parse.h that there are corresponding structures:
struct sequencewhich is a set of pipelines (one to be executed after the other),struct pipelinewhich is a set of commands whereby the output of the previous is piped to the input of the next, andstruct commandwhich is a program or builtin command followed by the arguments to pass to that program/command.
ls- a single command run immediately in the foreground.ls -l- a command with an argument.ls -1 | grep msh- a pipeline with two commands.ls | grep msh ; cd .. ; ls | grep msh- A sequence of three pipelines, the first and the third having two commands. Thegrepcommands have a single argument.
- Whitespace will include only spaces (not tabs or newlines).
For this milestone, you'll implement a first version of the shell that supports pipes.
This means that you'll need to actually execute the commands, passing to them their arguments, and set up the pipes and descriptors to support pipelines.
Pipes must be set up as follows: the standard output the command on the left of the | goes to a pipe, and the standard input of the command on the right.
For example, the command a b c d would attempt to execute a program called a, passing it arguments b, c, and d.
The commands a b | c d execute programs a and c passing them b and d, respectively, and the output from a is a pipe that provides the input for c.
This is, of course, meant to strongly mimic how normal shells work.
-
cd- change the current working directory. This takes a single argument, which is the directory to switch to. This must support the ability to change to a relative directory paths (e.g.cd .., orcd blah/), absolute paths (e.g.cd /home/gparmer, orcd /proc/), and paths relative to our home directory (e.g.cd ~to switch to the home directory, andcd ~/blahblah/to switch into theblahblahdirectory in our home directory.) An example:> pwd /home/gparmer/tmp > ls hw.c > cd ~ > pwd /home/gparmer > ls tmp > cd ~/tmp > pwd /home/gparmer/tmpWe will not support the general use of
~in arbitrary commands, instead only supporting it incd. -
exit- exit from the shell. As this milestone does not support background computation, no pipelines should be executing when we exit from the shell.
To exit from the shell , simply type an empty command, or cntl-d (hold the control button, and press "d").
You cannot change this behavior.
The testing harness requires that an empty command or a cntl-d exits the shell.
This is the default behavior of msh_main.c.
This requires the use of signals to coordinate between the shell, user, and children, and a set of builtin commands to control pipelines (which are synonymous with "job").
When a pipeline is executing in the foreground, the shell is waiting for it complete, thus will not receive additional inputs.
A pipeline is executed in the foreground by default.
If the & operator is provided at the end of a pipeline, it is executed in the background.
A foreground pipeline can be
- suspended with
cntl-z(that is, holding "control" and pressing "z") at which point the shell can receive input again, or - terminated with
cntl-c.
A suspended pipeline can be placed into the background with the bg command or into the foreground with fg.
Signals are used to suspend, continue, and terminate pipelines.
Signals of interest include the following^[See man 7 signal for more information.]
SIGINT- sent withcntl-cto the shellSIGTSTP- sent withcntl-zto the shellSIGTERM- sent to the pipeline processes, they will terminate
There are decent examples for similar commands and signals in bash.
sleep 10 &- a single command, an argument, and to be run in the background.sleep 10 & ; ls ; sleep 9- Three commands, the firstsleepruns in the background, thus the shell immediately executes thels, then the lastsleepwhich is run in the foreground, thus maintains the command line for9seconds.sleep 10 ; ls ; sleep 9- Since the firstsleepis run in the foreground, the shell waits for10seconds before proceeding to the latter commands. If the user typescntl-zwhile the first sleep is executing, it will be moved to the background, thus thelsis immediately executed.
This requires implementing the redirection operators introduced earlier. After a program and its arguments, a command can include a redirection of standard error to a file, then either
- a redirection of standard output via a pipe,
- a redirection of standard output to a file, or
- no redirection for standard output, thus sending its output to the command line.
echo hi 2> err.output | cat 2> caterrs.output- This will runecho, redirecting its standard error to theerr.outputfile, and redirect its standard output to the pipeline, and also redirect the errors forcatto a file. The standard output of thecatgoes, as normal, to the terminal.ls | grep msh 1>> output- The last command in a pipeline can redirect its output to a file, in this casegrep's output is appended to output.