An UNIX shell with custom features
Video demonstration available at: https://claud.pro/shell/
I implement each of the shell functionaily separately. I parse the command line with the strtok function and parse command line differently base on different input command. I use if statement to detect what the input command is to decide the way of parsing.
I create a struct called command that is used to store the parsed command line. It contains two members: cmd and args. cmd stores the command and args stors the command and arguments after command. I design it in this way because it fits the execvp arguments.
To replace system()
, I use fork()
to create process. I use an if else
statement to separate out child and parent. I use the function execvp()
to
execute the command. I parse the command line into arguments that fits the
execvp function
. In parent, I use waitpid()
to wait for the child
termination and get its status, and I print the completion message to
stderr
. In child, I execute command with execvp()
.
I parse the command line in order to differentiate which is command and which
is argument, I use strtok()
to separate arguments and command, I use
white space as delim. I store the arguments in a char array, so that
it can directly be passed in to the execvp()
function.
To implement build-in command, I first use if statements to detect whether the
input command line is calling one of the core feature built-in command, then
I use chdir()
to implement cd and getcwd()
for pwd.
For output redirection implementation, I use strchr()
to check whether the
command involves >
. strchr()
returns a pointer that contains the strings
following the argument. I accomplish this by detecting whether the return
pointer is NULL. Then, I get our destination file name from strchr()
by
incrementing the return pointer. Then, I use strtok()
again to separate out
the command and file name. I use dup2()
to connect the file descripter of
opening the file to stdout
.
For pipe command implementation, I first count the number of processes in the
command line, by doing a for loop and count the number of |
and then I
create number of proceses minus one of pipes using atwo-d integer array fd[][]
.
For the first process, I only connect its stdout to the first pipe.
For the last process, I only connect its stdin to the last pipe.
For any processes in the middle, I connect each process with
two pipes, connect the stdin to the previous pipe with stdout with the
previous process, and connect the stdout of the process to the next pipe
with stdin of the next process. I use a while loop to create multiple
children with the same parent.
For extra feature implementation, in pipe command or output redireciton
command, I detect whether &
lies right after |
or >
, if the symbol is
found, I parse the command line differently. I also use strtok()
but use
a different delim argument. This time, I use |&
or >&
to separate
command line. I then connect the stderr to the file descriptor just like
what I did to redirect output using dup2()
.
For Error handeling, I detect parsing error by adding for loops inside of the
command implementation to find out if there are missing command. For pipe
command, I detect missing command by comparing the nummber of |
and the
number of process. For library error handeling, I use a couple of perror()
under the function to catch error, and I also use fprintf()
for customize
error message printing.
I test our code by using printf and compare the output with the reference shell Bash for a variety of different command. For Pipe command testing, I first test by piping two processes. I print out the file descriptors in order to see if I connect everything right.