Assignment: Custom Shell Part 1 - Command Line Parser
This week we will begin the first of a multi-part assignment to build a custom shell called dsh
(Drexel shell).
What is a Shell?
A "shell" is a type of user interface for interacting with an operating system. You are already familiar with Linux command line shells in this course - the integrated terminal in vscode runs a shell (probably "bash" if you are using any of the Linux virtualization options suggested).
When we say your terminal "runs the bash shell" it means this: a shell is a generalized term for a binary that provides a terminal-based interface; "bash" is the name of a specific shell that you can install and run as a binary. Linux distributions have default shell configurations, and most of them default to bash
. You can install new shells and use them as your default shell upon login; zsh
is a popular shell that many users prefer over bash.
The purpose of the shell is to broker inputs and outputs to built-in shell commands, other binaries, and kernel operations like syscalls. When you open a terminal in vscode, the shell is interactive and you must provide inputs by typing in the terminal. Shells are also a part of "headless" processes like cron jobs (in Linux cron jobs run binaries on a schedule automatically, even if you are not logged in); these background jobs still execute in the context of a shell. The shell provides the job the same interface to the operating system, and the job can use the output that the shell brokers.
Shell built-in vs. external commands
Most "commands" you run are not implemented in the shell logic; they are usually other binaries on the filesystem that are invoked when you refer to them from your shell.
However ... there several (50+) commands that are built in to the logic of the shell itself. Here are some examples:
- cd: change the current working directory
- exit: exit from the shell process
- echo: print text to STDOUT
- export: set environment variable in the current shell
In this assignment, we will implement one "built in" command: exit
. There is also an optional dragon
command for extra credit.
Future assignments will generally follow the pattern of other popular shells and implement some of the common builtin commands.
What else does the shell do?
Beyond implementing built-in commands, a shell acts as a robust broker of input and output. Shells must provide a lot of "glue" to handle streams; for example this is a common sequence you might see in a linux shell:
run-some-command | grep "keyword"
The |
symbol is called a pipe, and it serves to stream the output of the first command (run-some-command
) into the input of the second command (grep
). We'll implement much of this "glue" logic in future assignments in this course.
Assignment Details
In this assignment you will implement the first part of dsh
: a command line parser to interpret commands and their arguments.
You will not implement any command logic, other than exiting when the exit
command is provided. There is also one optional extra credit for implementing the dragon
command.
./starter/dshlib.h
Step 1 - ReviewThe file ./starter/dshlib.h contains some useful definitions and types. Review the available resources in this file before you start coding - these are intended to make your work easier and more robust!
./starter/dsh_cli.c
Step 2 - ImplementThis contains the entrypoint of your shell. Detailed comments are provided to implement main
.
Shells usually run in a forever loop until a command is issued to exit the shell (usually exit
); an example is provided in comments to get you started:
while (1)
{
printf("%s", SH_PROMPT);
if (fgets(cmd_buff, ARG_MAX, stdin) == NULL)
{
printf("\n");
break;
}
// remove the trailing \n from cmd_buff
cmd_buff[strcspn(cmd_buff, "\n")] = '\0';
// IMPLEMENT THE REST OF THE REQUIREMENTS
}
The libc fgets
function is a good choice here due to being "lines of input" based. This excerpt from man 3 fgets
explains why:
fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.
Key point, Reading stops after an EOF or a newline
- which makes it essentially a "line by line" processor. This is an important detail for step 3!
exit
Step 2a - Implement the built-in function Inside the main loop in main()
you can check for and implement the logic for the exit
command. When this command is issued, your process should exit with a 0
exit code.
This is also similar to how you would implement the extra credit - instead of existing, you would print more output to STDOUT.
./starter/dshlib.c
Step 3 - ImplementIn this file you need to complete the implementation for build_cmd_list
:
int build_cmd_list(char *cmd_line, command_list_t *clist)
{
printf(M_NOT_IMPL);
return EXIT_NOT_IMPL;
}
This takes two parameters:
- cmd_line - one complete "line" of user input from the shell; remember
dsh_cli.c
should use EOF / newline (i.e.fgets()
) to read one line at a time - clist - pointer to a
command_list_t
; you must populate this structure with parsed commands
Remember - the goal of this assignment is not to implement command logic, but to parse the input string and populate clist
with parsed commands and their arguments. The comments provide details on logic and references to some helpful definitions.
Step 4 - Answer Questions
Answer the questions located in ./questions.md.
Sample Run with Sample Output
The below shows a sample run executing multiple commands and the expected program output:
➜ solution git:(main) ✗ ./dsh
dsh> cmd
PARSED COMMAND LINE - TOTAL COMMANDS 1
<1> cmd
dsh> cmd_args a1 a2 -a3 --a4
PARSED COMMAND LINE - TOTAL COMMANDS 1
<1> cmd_args [a1 a2 -a3 --a4]
dsh> dragon
[DRAGON for extra credit would print here]
dsh> cmd1 | cmd2
PARSED COMMAND LINE - TOTAL COMMANDS 2
<1> cmd1
<2> cmd2
dsh> cmda1 a1 a2 | cmda2 a3 a4 | cmd3
PARSED COMMAND LINE - TOTAL COMMANDS 3
<1> cmda1 [a1 a2]
<2> cmda2 [a3 a4]
<3> cmd3
dsh>
warning: no commands provided
dsh> c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
PARSED COMMAND LINE - TOTAL COMMANDS 8
<1> c1
<2> c2
<3> c3
<4> c4
<5> c5
<6> c6
<7> c7
<8> c8
dsh> c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9
error: piping limited to 8 commands
dsh> pipe1|pipe2|pipe3 |pipe4
PARSED COMMAND LINE - TOTAL COMMANDS 4
<1> pipe1
<2> pipe2
<3> pipe3
<4> pipe4
dsh> pipe1|pipe2 |pipe3 pipe4| pipe5
PARSED COMMAND LINE - TOTAL COMMANDS 4
<1> pipe1
<2> pipe2
<3> pipe3 [pipe4]
<4> pipe5
dsh> exit
➜ solution git:(main) ✗
Extra Credit: +5
Add logic to detect the command "dragon
" and print the Drexel dragon in ascii art.
Drexel dragon in ascii with spaces preserved:
@%%%%
%%%%%%
%%%%%%
% %%%%%%% @
%%%%%%%%%% %%%%%%%
%%%%%%% %%%%@ %%%%%%%%%%%%@ %%%%%% @%%%%
%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%% %%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%@ @%%%%%%%%%%%%%%%%%% %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%@
%%%%%%%%@ %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%
%%%%%%%%%%%%% %%@%%%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%%% @%
%%%%%%%%%% %%% %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% % %%%%%%%%%%%%% %%%%%%%%%%%%@%%%%%%%%%%%
%%%%%%%%%@ % %%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%@ %%@%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%@ %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%% %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%
%%%%%%%%%@ @%%%%%%%%%%%%%% %%%%%%%%%%%%@ %%%% %%%%%%%%%%%%%%%%% %%%%%%%%
%%%%%%%%%% %%%%%%%%%%%%%%%%% %%%%%%%%%%%%% %%%%%%%%%%%%%%%%%% %%%%%%%%%
%%%%%%%%%@%%@ %%%%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% %%
%%%%%%%%%% % %%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%
%%%%%%%%%%%% @ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%
%%%%%%%%%%%%% %% % %@ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%
@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% @%%%%%%%%%
%%%%%%%%%%%%%%%%%%%% @%@% @%%%%%%%%%%%%%%%%%% %%%
%%%%%%%%%%%%%%% %%%%%%%%%% %%%%%%%%%%%%%%% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% %%% %%%%%%%%%% %%%@
%%%%%%%%%%%%%%%%%%% %%%%%% %% %%%%%%%%%%%%%@
%%%%%%%@
Extra Credit: ++5
Implement the extra credit for the /dragon
using compressed/binary data in your code to represent the dragon (i.e. not just using strings/defines).
Grading Rubric
This assignment will be weighted 50 points.
- 25 points: Correct implementation of required functionality
- 5 points: Code quality (how easy is your solution to follow)
- 15 points: Answering the written questions: questions.md
- 5 points: [EXTRA CREDIT] Implementation of the
dragon
command - 5 points: [EXTRA CREDIT++] Implementation of the
dragon
command where the representation of the dragon is compressed in your code
Total points achievable is 60/50.
Automated Testing
In order to help you out the starter package provides a testing script called test.sh
. You should expect your tests to fail when running the starter and then start to pass as you implement the required functionality. Also note a few of the test cases are commented out.