ECS 150: Project #1 – Simple Shell
ECS 150: Project #1 – Simple Shell
Copyright By PowCoder代写 加微信 powcoder
Prof. Joël Porquet-Lupine
UC Davis, Fall Quarter 2022
information
Objectives of the project
description
Introduction
Constraints
Assessment
The sshell
specifications
Commands and command line
redirection
management
Reference program and
Suggested work phases
Phase 0: preliminary work
Phase 1: running
simple commands the hard way
Phase 3: builtin commands
Phase 4: Output redirection
Phase 5: Pipeline commands
Phase 6: Extra feature(s)
Submission
Gradescope
The specifications for this project are subject to change at
anytime for additional clarification. Make sure to always refer to the
latest version.
v2: Error message when conflict between piping and input
redirection
v1: First publication
General information
Due before 11:59 PM, Thursday, October 13th,
You will be working with a partner for this project.
The reference work environment is the CSIF.
Objectives of the project
The objectives of this programming project are:
Reviewing most of the concepts learned in previous programming
courses: data structures, file manipulation, command line arguments,
Makefile, etc.
Discovering and making use of many of system calls that UNIX-like
operating systems typically offer, especially syscalls belonging to the
following categories: processes, files, and pipes.
Understanding how a shell works behind the hood, and how processes
are launched and configured.
Writing high-quality C code by following established industry
standards.
Program description
Introduction
The goal of this project is to understand important UNIX system calls
by implementing a simple shell called sshell. A shell
is a command-line interpreter: it accepts input from the user under the
form of command lines and executes them. Well-known UNIX shells include
for example bash (default shell on Ubuntu) and
zsh (default shell on MacOS).
In the following example, it is the shell that is in charge of
printing the shell prompt, understanding the supplied command
line (redirect the output of executable program date to the
input of executable program tr with arguments
2 and 1), execute it, and wait for it to
finish before displaying a completion message and prompting the user for
a new command line.
date | tr 2 1
Thu 07 Jan 1011 06:40:47 PM PST
+ completed ‘date | tr 2 1’ [0][0]
Your shell will provide the following set of core features:
Execution of user-supplied commands with optional arguments
Selection of typical builtin commands
Redirection of the standard output of commands to files
Composition of commands via piping
In addition, your shell will provide the following extra feature:
Redirection of the standard error of commands to files or pipes
Simple ls-like builtin command
Constraints
Your code must be written in C, be compiled with GCC and only use the
standard functions provided by the GNU C Library (aka
libc). All the functions provided by the
libc can be used, but your program cannot be linked to any
other external libraries.
Your source code should adopt a sane and consistent coding style and
be properly commented when necessary. One good option is to follow the
relevant parts of the Linux
kernel coding style.
Assessment
Your grade for this assignment will be broken down in two scores:
Auto-grading: ~60% of grade
Running an auto-grading script that tests your program and checks the
output against various inputs
Manual review: ~40% of grade
The manual review is itself broken down into different rubrics:
Submission : ~10%
Report file: ~40%
Makefile: ~10%
Quality of implementation: ~30%
Code style: ~10%
The sshell
specifications
Commands and command line
When the shell is ready to accept input from the user, it must print
’ – without the quotes but with the trailing
white space. At this point, the user can type a single command, or a
pipeline of commands. Each command starts with the name of a program
(e.g. ls, ps, cat,
echo) and is optionally followed by arguments, separated by
one or more white spaces, to be passed to the program
(e.g. ls -l).
The shell may assume that:
The maximum length of a command line never exceeds 512
characters.
A program has a maximum of 16 arguments.
The maximum length of individual tokens never exceeds 32
characters.
Since it would be annoying for the user to always type the complete
paths of the commands to execute (e.g. /bin/ls), programs
should be searched according to the $PATH
environment variable.
After the shell launches the command(s) corresponding to the command
line, it waits until all the commands have finished. Only then, the
shell displays a completion message on stderr, which
contains the return values of the completed commands, and displays a new
prompt for a new input command line to be supplied.
echo Hello world
Hello world
+ completed ‘echo Hello world’ [0]
In addition to programs and their arguments, the shell must
understand certain specific meta-characters (e.g. >,
|, etc.) as described in the next sections.
Builtin commands
When a user enters a command, the related program is usually an
external executable file. For example, ls refers
to the executable file /bin/ls while fdisk
refers to /sbin/fdisk (this is abstracted by the
$PATH, as mentioned above).
For some commands, it is preferable, or even necessary, that the
shell itself implements the command instead of running an external
program. As part of the core features, your shell must implement the
commands exit, cd and pwd.
pwd can actually be implemented by an external program
(and is often provided as such on most UNIX systems), but we decide for
this project that it should be provided by the shell itself.
For simplicity, you may assume that these builtin commands will never
be called with incorrect arguments (i.e. no arguments for
exit and pwd and exactly one argument for
Receiving the builtin command exit should cause the
shell to exit properly (i.e. with exit status 0). Before
exiting, the shell must print the message ‘Bye…’ on
$ ./sshell
+ completed ‘exit’ [0]
cd and pwd
The user can change the current working directory (i.e. the
directory the shell is currently “in”) with cd or display
it with pwd.
/home/jporquet/ecs150
+ completed ‘pwd’ [0]
+ completed ‘cd ..’ [0]
/home/jporquet
+ completed ‘pwd’ [0]
Output redirection
The standard output redirection is indicated by using the
meta-character > followed by a file name. Such
redirection implies that the command located right before
> is to write its output to the specified file instead
of the shell’s standard output (that is on the screen if the shell is
run in a terminal).
echo Hello world>file
+ completed ‘echo Hello world>file’ [0]
Hello world
+ completed ‘cat file’ [0]
You can assume that output redirection will never be used with
builtin commands.
The pipe sign is indicated by using the meta-character |
and allows multiple commands to be connected to each other within the
same command line. When the shell encounters a pipe sign, it indicates
that the output of the command located before the pipe sign must be
connected to the input of the command located after the pipe sign. We
assume that there can be up to three pipe signs on the same command line
to connect multiple commands to each other.
echo Hello world | grep Hello|wc -l
+ completed ‘echo Hello world | grep Hello|wc -l’ [0][0][0]
The completion message must display the exit value of each command
composing the pipeline separately. This means that commands may have
different exit values as shown in the example below (the first command
succeeds while the second command fails with exit value 2).
echo hello | ls file_that_doesnt_exists
ls: cannot access ‘file_that_doesnt_exists’: No such file or directory
+ completed ‘echo hello | ls file_that_doesnt_exists’ [0][2]
You can assume that builtin commands will never be called as part of
a pipeline.
In a pipeline of commands, only the last command may have its output
redirected.
Error management
There are three types of errors that the shell needs to deal
Failure of library functions.
Errors during the parsing of the command line.
Errors during the launching of the command line.
Failure of library functions
If a library function fails, for example if malloc() is
unable to allocate memory or if fork() is unable to spawn a
child, then the shell is allowed to terminate its execution right away.
You may optionally use perror() to report the cause of the
Parsing errors
If an incorrect command line is supplied by the user, the shell
should only display an error message on stderr, discard the
invalid input and wait for a new input, but it should
If a command line contains more than one parsing error, the leftmost
one should be detected first and reported.
Here are all the potential parsing errors for the core features:
“Error: too many process arguments”
ls 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Error: too many process arguments
“Error: missing command”
Error: missing command
Error: missing command
Error: missing command
“Error: no output file”
Error: no output file
“Error: cannot open output file”
echo hack > /etc/passwd
Error: cannot open output file
“Error: mislocated output redirection”
echo Hello world > file | cat file
Error: mislocated output redirection
Launching errors
Launching errors are usually not detected during parsing, but instead
when the shell actually tries to execute the parsed command line. Like
parsing errors, launching errors should not cause the shell to die. The
command launch should gracefully fail and the shell should ask for a new
Here are all the potential launching errors for the core
“Error: cannot cd into directory”
cd doesnotexist
Error: cannot cd into directory
+ completed ‘cd doesnotexist’ [1]
“Error: command not found”
Error: command not found
+ completed ‘windows98’ [1]
Extra features
This quarter, there are two additional features that your shell must
implement.
Standard input redirection
In addition to output redirection, your shell should implement input
redirection as well.
The standard input redirection is indicated by using the
meta-character < followed by a file name. Such
redirection implies that the command located right before
< is to read its input from the specified file instead
of the shell’s standard input (that is from the keyboard if the shell is
run in a terminal).
+ completed 'cat file' [0]
grep toto
$ cat your_output
echo Hello
+ completed ‘echo Hello’ [0]
+ completed ‘exit’ [0]
$ echo -e “echo Hello\nexit\n” | ./sshell_ref >& ref_output
$ diff your_output ref_output
Using a simple bash script, you can easily turn every single example
contained in this document into a test case.
Suggested work phases
The following phases are merely a suggestion to help you progress
step by step, but you are under no obligation to follow them!
The phases do provide additional information so it is still worth
reading through them.
Phase 0: preliminary work
A skeleton C file is provided in
/home/cs150jp/public/p1/sshell.c to help you start this
project. Copy it to your directory. Compile it into an executable named
sshell and run it.
$ ./sshell
echo Hello
Return status value for ‘echo Hello’: 0
It’s already a very simple shell!
0.1 Understand the code
Open the C file and read the code. As you can notice, we use the
rudimentary function system() to run commands. The problem
is that system() is too high-level to use for implementing
a realistic shell. For example, it doesn’t support output redirection or
Useful resources for this phase:
man system
Libc – Running a command
0.2 Makefile
Write a simple Makefile that generates an executable
sshell from the file sshell.c, using GCC.
The compiler should be run with the -Wall -Wextra
(enable all warnings, and some more) and -Werror (treat all
warnings as errors) flags.
There should also be a clean rule that removes any
generated files and puts the directory back in its original state.
Useful resources for this phase:
Make – Manual
Warning options
Phase 1: running
simple commands the hard way
Instead of using the function system(), modify the
program in order to use the fork+exec+wait method, as seen in
lecture. For this phase, start by focusing on simple commands with no
arguments.
In a nutshell, your shell should fork and create a child process; the
child process should run the specified command with exec while the
parent process waits until the child process has completed and the
parent is able to collect its exit status.
In order to automatically search programs in the $PATH,
you simply need to carefully choose which of the exec
functions should be used (see first link below).
Fri 10 Jan 2020 12:31:13 AM PST
+ completed ‘date’ [0]
There are a couple of non-apparent differences between this output
and the output of the provided skeleton code:
The completion message following the execution of the command is
printed to stderr and not stdout.
The printed status (i.e. 0 in the example above) is not
the full raw status value anymore, it is the exit
status only. Refer to the Process Completion Status section of
the libc documentation to understand how to extract this
value (see second link below).
Useful resources for this phase:
Libc – Executing a file
Libc – Processes
Phase 2: arguments
In this phase, you can now add to your shell the ability to handle
command lines containing programs and their arguments.
For this phase, you will need to really start parsing the
command line in order to interpret what needs to be run. Refer to the
libc documentation to learn more about strings in C (and
particularly sections 5.1, 5.3, 5.4, 5.7 and 5.10): GNU
Libc – String and array utilities.
Example of commands which include arguments (with more or less white
spaces separating arguments):
Tue Apr 4 22:07:03 UTC 2017
+ completed ‘date -u’ [0]
date -u
Tue Apr 4 22:46:41 UTC 2017
+ completed ‘date -u’ [0]
At this point, and if you have not already, it probably is the right
time to think of how you could represent commands using proper data
structures. After all, a struct object in C is nothing
different than a C++/Java class without methods. But such an object can
still contain fields that contain the object’s properties, and C++-like
methods can be implemented as simple functions that receive objects as
parameters.
/* C++ class */
class myclass {
mymethod(int b) {
/* Equivalent in C */
struct myobj {
myfunc(struct myobj *obj, int b) {
obj->a = b;
The result of parsing the command line should be the instance of a
data structure which contains all the information necessary to launch
the specified command (so that the original command line does not have
to be parsed again).
Phase 3: builtin commands
Implement the rest of the builtin commands, namely pwd
Useful resources for this phase:
Libc – Working directory
Phase 4: Output redirection
Implement output redirection. There are two steps to this
implementation
Parsing of the output redirection (meta-character and output file)
from the command line. Note that the output redirection is only an
instruction to the shell, it should not be transmitted to the program as
arguments.
Manipulation of the stdout file descriptor prior to
running the specified program, so that the program prints into the file
and not to the terminal.
Note that the output redirection symbol may or not be surrounded by
white spaces.
If the output file already exists, it should be truncated. See
options to open() to figure out the correct flag.
Phase 5: Pipeline commands
Implement piping. For this phase, you will probably need to think of
a data structure that can be used to represent a job (i.e. a pipeline of
one or more commands).
Given that pipes have a limited size, commands must be run
concurrently (rather than one strictly after the other) in order to
allow for arbitrary amounts of data to be transmitted from one program
to the next.
cat /dev/urandom | base64 -w 80 | head -5
+JnHYFpmAI8OsHaBOrWHIDgvJ97sXAxgkqqaNTifL+2I5crtRcduWRRfFl6675slNWzO4Zv5o6SdVLY3
g1QksjAsYMGkBOIrasHmOXuzyeam2raXdq1yqa6H2QXyOrcJx8FU1QRRm8MY0YAc6ccf4lIA91tmrZGl
r0IrSYG5eFDDqk6hXoiPcZX4X8jWy4ihG1WGvjDi99O5peh8r0+Kifp7jDnyc+/L8kxRUbygfWS1rPKz
tXellbcgxDa78nS+NlJDMOuJzTfd7iYh5Jx5W5qJVBEP0wCqmtzAiwfIS+ODw3w2DiPaFuI2odIXq8Jj
n9GxVqyUhWDBJ7W2tAPGxYX/HCHFVmC6zRK7oZJIytXg3LUfLRYvR/VQZd7y8Vyiw/5VxjKINeU3Bxmg
+ completed ‘cat /dev/urandom | base64 -w 80 | head -5’ [0][0][0]
Useful resources for this phase (sections 15.1 and 15.2): GNU
Libc – Pipes and FIFOs.
Phase 6: Extra feature(s)
Once you have completed the set of core features, add the extra
feature(s) to your shell.
The redirection of the standard input follows a similar logic as for
the output redirection, bo
程序代写 CS代考 加微信: powcoder QQ: 1823890830 Email: powcoder@163.com