Project 2: Command Line Shell (v 1.0)
The outermost layer of the operating system is called the shell. In Unix-based systems, the shell is generally a command line interface. Most Linux distributions ship with bash
as the default (there are several others: csh
, ksh
, sh
, tcsh
, zsh
). In this project, we’ll be implementing a shell of our own – see, I told you that you’d come to love command line interfaces in this class!
You will need to come up with a name for your shell first. In the following examples, my shell is named crash
(Cool Really Awesome Shell) because of its tendency to crash.
The Basics
Upon startup, your shell will print its prompt and wait for user input. Your shell should be able to run commands with exec
. If a command isn’t found, print an error message:
[0]-[1]─[/]$ ./hello
Hello world!
[0]-[2]─[/]$ ls /dir
(directory contents are shown by ls)
[0]-[3]─[/]$ echo hello there!
hello there!
[0]-[4]─[/]$ cd /this/does/not/exist
chdir: no such file or directory: /this/does/not/exist
[1]-[5]─[/]$ cd /this/does/exist
[0]-[6]─[/this/does/exist]$
Prompt
The shell prompt displays some helpful information. You can customize it to look however you want, but at a minimum, it must include:
- Exit status of the last command
- Command number (starting from 1)
- The current working directory
In the example above, these are separated by dashes and brackets to make it a little easier to read. You’ll notice that when the cd
built in command failed, the exit status was “1”.
Scripting Support
Your shell must support scripting mode, which also makes it easier to test. If argv[1]
is a file, read commands from it instead of from stdin
:
$ cat test.sh
ls /
echo hi
mkdir d
cd d
ls
/ls
/echo bye
exit
$ crash test.sh
(ls / contents appear)
hi
exec ls failed
. 1 35 32
.. 1 1 1024
bye
You should check and make sure you can run a large script with your shell. Note that the shell prompt is not displayed when in scripting mode, and the script should not have to end with the exit
command, even though it does here.
Executable Scripts
Beyond basic scripting support, you should also support executing scripts that begin with a “shebang” (#!
), for instance #!/crash
at the beginning of the script would execute the script with your shell. Executable script support is mainly provided via the OS program loader; you should modify the kernel to detect the presence of #!
at the beginning of a shell script and then execute the interpreter with the script name as its first command line argument.
Here’s the same example as above, but with executable script support:
$ cat test.sh
#!/crash
ls /
echo hi
mkdir d
cd d
ls
/ls
/echo bye
exit
$ ./test.sh # notice how we didn't have to run 'crash' here
(ls / contents appear)
hi
exec ls failed
. 1 35 32
.. 1 1 1024
bye
Built-In Commands
Most shells have built-in commands, including cd
and exit
. Your shell must support:
cd
to change the CWD.#
(comments): strings prefixed with#
will be ignored by the shellhistory
, which prints the last 100 commands entered with their command numbers!
(history execution): entering!39
will re-run command number 39, and!!
re-runs the last command that was entered.!ls
re-runs the last command that starts with ‘ls.’ Note that command numbers are NOT the same as the array positions; e.g., you may have 100 history elements, with command numbers 600 – 699.exit
to exit the shell.
History
Here’s a demonstration of the history
command:
[0]-[142]─[/]$ history
42 ls
43 cat long-text.txt
44 echo "hi" # This prints out 'hi'
... (commands removed for brevity) ...
140 ls /bin
141 gcc -g crash.c
In this demo, the user has entered 142 commands. Only the last 100 are kept, so the list starts at command 43. If the user enters a blank command, it should not be shown in the history or increment the command counter.
Here’s the second form of the history command, history -t
. This version shows the amount of time each command ran for:
[0]-[142]─[/]$ history -t
[42|68ms] ls
[43|982ms] cat long-text.txt
[44|13ms] echo "hi" # This prints out 'hi'
... (commands removed for brevity) ...
[140|32ms] ls /bin
[141|1ms] gcc -g crash.c
In both cases, the history
command is not shown at the end of the output. For background processes, report the amount of time it took for the child process to start.
I/O Redirection
Your shell must support file input/output redirection and pipes:
# Create/overwrite 'my_file.txt' and redirect the output of echo there:
[0]-[142]─[/]$ echo "hello world!" > my_file.txt
[0]-[143]─[/]$ cat my_file.txt
hello world!
# Append text with '>>':
[0]-[144]─[/]$ echo "hello world!" >> my_file.txt
[0]-[145]─[/]$ cat my_file.txt
hello world!
hello world!
# Pipe:
[0]-[146]─[/]$ cat other_file.txt | sort
(sorted contents shown)
[0]-[147]─[/]$ seq 100000 | wc -l
10000
[0]-[148]─[/]$ cat /etc/passwd | sort > sorted_pwd.txt
(sorted contents written to 'sorted_pwd.txt')
# This is equivalent, but uses input redirection instead of cat:
[0]-[149]─[/]$ sort < /etc/passwd > sorted_pwd.txt
# This is equivalent as well. Order of < and > don't matter:
[0]-[150]─[/]$ sort > sorted_pwd.txt < /etc/passwd
# Here's input redirection by itself (not redirecting to a file):
[0]-[151]─[/]$ sort < sorted_pwd.txt
(sorted contents shown)
Use dup
and pipe
to provide redirection and pipe support. Your implementation should support arbitrary numbers of pipes, but you only need to support one file redirection per command line (i.e., a >
or >>
in the middle of a pipeline is not something you need to consider).
Background Jobs
If a command ends in &
, then it should run in the background (do not pass &
to the program as an argument). In other words, don’t wait for the command to finish before prompting for the next command.
Path Execution
You may have noticed that when you leave the /
directory (cd somedir
), suddenly programs begin failing to run unless you prefix them with a /
, e.g., cat
doesn’t work but /cat
does. This is because we don’t have support for path execution – searching a path or set of paths for binaries. Instead, the default behavior is to simply run whatever is in the current directory. For this feature, you will create a new function in your shell called execvp(pathname, args)
that will search for and run binaries based on priority. The priorities are:
- Absolute or relative paths are run first. E.g.,
../ls
runs thels
binary in the directory one level above,/cat
runscat
from the root directory, etc. - If an absolute/relative path wasn’t given, and the user simply typed a program’s name, then search the
/
(root) directory for it by default. - If the previous step failed (the program doesn’t exist in
/
), then attempt to execute it from the current directory instead.
Make sure that your execvp
function follows the hierarchy above.
Signature Feature
After you’re done implementing all the features above, you will need to choose to add one more feature to your shell. You can take inspiration from other shells, or come up with something completely new. It’s your choice – just make sure you run the idea by the instructor first.
Documentation
You must include documentation for your shell, located in docs/SHELLNAME.md
. The documentation must describe your program, how it works, and any other relevant details. You’ll be happy you did this later if/when your revisit the codebase. Here is an example README.md file.
Kernel Changes
You will need to make the following modifications to your OS kernel as part of this project:
- Allow retrieval of the real time clock for tracking process run times
- Add a system call to retrieve the CWD as a string
- Modify the program loader to support shell scripts
- File append support (for the
>>
operator). You should add anO_APPEND
open mode for this.
Grading and Submission
Check your changes into your OS repo as you work. We’ll do a live demo and code review to grade the project, so you should test your shell with a variety of commands to make sure it will work during the demo.
This project is worth 18 points.
To receive 60% credit, implement:
- Basic command execution (use
fork
,exec
, and split up arguments based on whitespace) - The shell prompt
- Basic scripting support (script file passed in as first argument)
To receive 75% credit, implement:
- All previous requirements
- Basic built in commands (
cd
,#
,history
,exit
)
To receive 85% credit, implement:
- All previous requirements
- History execution (
!num
,!prefix
,!!
) - Project documentation, checked in as
docs/SHELLNAME.md
To receive 90% credit, implement:
- All previous requirements
- Pipe and redirection support
O_APPEND
To receive 95% credit, implement:
- All previous requirements
- Executable script support
- Path execution (
execvp
) - Background jobs
To receive full credit for this project:
- All previous requirements
- Implement your shell’s signature feature
Your grade will also include an additional 2 points from the code review / demo. Things that we will check:
- Prompt and general interactive functionality. We’ll run your shell to test this with a few commands.
- If the reviewer crashes your shell during the demo, you will lose 0.25 points per instance.
- Code walkthrough for quality and stylistic consistency
- You will be asked to explain 1-3 parts of your implementation. You should be able to describe high-level design aspects and the challenges you faced implementing the features.
- Functions, structs, etc. have documentation where appropriate
- No dead, leftover, or unnecessary code.