Due: Wednesday, Oct 30, 7:00am
In this project, you will implement a basic shell called bsh. bsh won't implement any of the fancy features of Linux's standard shells, such as dash or bash, but it will implement pipelines (|) and redirection of stdout (>) and stdin (<), and in this way capture the essence of a shell and be somewhat useful.
Print a usage statement to stdout and exit with status 0.
To make parsing a little easier, we will require that input redirection may only occur for the first command and output redirection only for the first and/or last command in a pipeline. (If a pipeline consists of a single command, then that command can redirect both stdin and stdout.) Moreover, there can be no spaces between the operator and the filename:
# (1) right
cat <in.txt | sed s/foo/bar/g >out.txt
# (2) wrong (spaces between operator and filename)
cat < in.txt | sed s/foo/bar/g > out.txt
Note that in (2) above, bsh interprets < in.txt as two separate arguments to the cat command. Similarly, bsh interprets > out.txt as two separate arguments to the sed command.
To help get started, please use the following skeleton code bsh_skeleton.zip.
Note that bsh.c already implements a loop that prompts the user for a command, parses the command, and then prints the result of the parsing. In particular, the skeleton code already handles parsing a pipeline of commands into a linked list (represented by struct pipeline). In turn, each command is represented by a struct cmd that uses a dynamically-sized array of strings to store the command and its arguments.
Within bsh.c, there are a few TODO comments that correspond to code you need to write. In particular, you must implement the usual getopt_long parsing of bsh's options, implement parsing of the I/O redirection operators (if present, the skeleton code's parsing will have included these as the last arguments to the last command in the pipeline), and finally implement the actual evaluation of the pipeline.
Implement a shell builtin called last_error. A shell builtin is a command that the shell implements as an internal function: the shell invokes the function instead of exec'ing an external program. In general, the shell uses builtins to implement commands that would be impossible to implement as an external program (e.g., pwd), or for commands that are much more efficient to implement internally (e.g., echo).
Your last_error command is conceptually equivalent to echo $?: it prints to stdout the exit status of the last command executed, followed by a newline. If the last command executed was a pipeline of multiple commands, then last_error prints the exit status of the last command in that pipeline.
To keep things simple, we'll assume that last_error is always called by itself: it is never part of pipeline and never redirects its output.
Submit your project as a zip file via gradescope. Your project must include a Makefile that builds an executable called bsh. Please refer to the instructions for submitting an assignment for details on how to login to gradescope and properly zip your project.
./bsh --help
Prints a usage statement to stdout. The statement must start with either Usage or usage; you decide the rest of the message. Conventionally, this option either prints the synopsis or a more verbose statement that also includes a description of the options.
./bsh -h
echo $?
0
The exit status is zero.
./bsh
(out) > cat machado.txt
(out) Caminante, son tus huellas
(out) el camino, y nada más;
(out) caminante, no hay camino,
(out) se hace camino al andar.
(out) Al andar se hace camino,
(out) y al volver la vista atrás
(out) se ve la senda que nunca
(out) se ha de volver a pisar.
(out) Caminante, no hay camino,
(out) sino estelas en la mar.
(out) >
Prints the file.
./bsh
(out) > head -n2 hughes.txt
(out) Hold fast to dreams
(out) For if dreams die
(out) >
Prints the first two lines.
./bsh
(out) > cat angelou.txt | wc -l
(out) 15
(out) >
Has the above output.
./bsh
(out) > cat machado.txt | sed s/camino/CAMINO/g | head -n3
(out) Caminante, son tus huellas
(out) el CAMINO, y nada más;
(out) caminante, no hay CAMINO,
(out) >
Has the above output.
./bsh
(out) > cat <hughes.txt
(out) Hold fast to dreams
(out) For if dreams die
(out) Life is a broken-winged bird
(out) That cannot fly.
(out) Hold fast to dreams
(out) For when dreams go
(out) Life is a barren field
(out) Frozen with snow.
(out) >
Prints the entire file.
./bsh
(out) > head -n2 machado.txt >a.txt
(out) >
Writes the first two lines of the file to a.txt.
./bsh
(out) > head -n2 <angelou.txt >b.txt
(out) >
Writes the first two lines of the file to b.txt.
./bsh
(out) > head -n2 >c.txt <angelou.txt
(out) >
Writes the first two lines of the file to c.txt.
./bsh
(out) > cat machado.txt | wc -l >a.txt
(out) >
Writes 10 followed by a newline to a.txt.
./bsh
(out) > cat <machado.txt | sed s/camino/CAMINO/g | head -n3
(out) Caminante, son tus huellas
(out) el CAMINO, y nada más;
(out) caminante, no hay CAMINO,
(out) >
Prints the first three lines of the file with camino in caps.
./bsh
(out) > cat <machado.txt | sed s/camino/CAMINO/g | head -n3 >a.txt
(out) >
Writes the first three lines of the file with camino in caps to a.txt.
./bsh
(out) > head -n1 machado.txt >a.txt
(out) > tail -n1 machado.txt >>a.txt
Writes the first line and last line the file to a.txt.
./bsh
(out) > head -n1 machado.txt >b.txt
(out) > tail -n1 <machado.txt >>b.txt
Writes the first line and last line the file to b.txt.
./bsh
(out) > head -n1 hughes.txt >c.txt
(out) > tail -n1 hughes.txt| sed s/snow/SNOW/g >>c.txt
Writes the first line and last line of the file to c.txt, with the last line having the word snow in caps.
./bsh
(out) > grep -q camino machado.txt
(out) > last_error
(out) 0
(out) >
Has the above output.
./bsh
(out) > grep -q zapato machado.txt
(out) > last_error
(out) 1
(out) >
Has the above output.
./bsh
(out) > cat machado.txt | sed s/camino/CAMINO/g | grep -q camino
(out) > last_error
(out) 1
(out) >
Has the above output.