CSCI 415/515: Fall 2023
Systems Programming
Project 4: bsh

Due: Monday, 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.

Name

bsh - basic shell

Synopsis

bsh [-h]

Options

-h, --help

Print a usage statement to stdout and exit with status 0.

I/O Redirection

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 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.

Skeleton Code

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.

UPDATE: The following zip file contains the code we wrote in class to begin fleshing out the skeleton.

Simplifying Assumptions

Other than pipes and redirection of stdout and stdin, our shell does not support any other features. In particular, we won't worry about argument quoting and variable expansion -- all of that requires a much more sophisticated parser.

Bonus 1

Implement the >>FILE redirection operator, which appends standard output to FILE. As with the other redirection operators, we will use a simplified syntax where there are no spaces between >> and FILE.

Bonus 2

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.

Submitting

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.

Rubric

Input Files

angelou.txt


      
hughes.txt


      
machado.txt



      

-h, --help


1.1 Print a usage statement (3 pts)


        ./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.

1.2 Zero exit status (2 pts)


        ./bsh -h
        echo $?
        0
        

The exit status is zero.

Single Command


2.1 cat (6 pts)


        ./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.

2.2 head (6 pts)


        ./bsh
(out) > head -n2 hughes.txt
(out) Hold fast to dreams
(out) For if dreams die
(out) >
        

Prints the first two lines.

Pipeline


3.1 cat | wc (8 pts)


        ./bsh
(out) > cat angelou.txt | wc -l
(out) 15
(out) >
        

Has the above output.

3.2 cat | sed | head (9 pts)


        ./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.

Single Command I/O Redirect


4.1 Redirect stdin (8 pts)


        ./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.

4.2 Redirect stdout (8 pts)


        ./bsh
(out) > head -n2 machado.txt >a.txt
(out) >
        

Writes the first two lines of the file to a.txt.

4.3 Redirect stdin and stdout (9 pts)


        ./bsh
(out) > head -n2 <angelou.txt >b.txt
(out) >
        

Writes the first two lines of the file to b.txt.

4.4 Redirect stdout and stdin (9 pts)


        ./bsh
(out) > head -n2 >c.txt <angelou.txt
(out) >
        

Writes the first two lines of the file to c.txt.

Pipeline I/O Redirect


5.1 cat | wc > (10 pts)


        ./bsh
(out) > cat machado.txt | wc -l >a.txt
(out) >
        

Writes 10 followed by a newline to a.txt.

5.2 cat | sed | head < (10 pts)


        ./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.

5.3 cat | sed | head < > (12 pts)


        ./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.

Bonus 1: Append (>>) I/O Redirection Operator


100.1 Single Command stdout(3 pts)


        ./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.

100.2 Single Command stdin and stdout(3 pts)


        ./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.

100.3 Pipeline stdout(4 pts)


        ./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.

Bonus 2: last_error builtin


200.1 grep succeed (3 pts)


        ./bsh
(out) > grep -q camino machado.txt
(out) > last_error
(out) 0
(out) >
        

Has the above output.

200.2 grep fail (3 pts)


        ./bsh
(out) > grep -q zapato machado.txt
(out) > last_error
(out) 1
(out) >
        

Has the above output.

200.3 Pipeline fail (4 pts)


        ./bsh
(out) > cat machado.txt | sed s/camino/CAMINO/g | grep -q camino
(out) > last_error
(out) 1
(out) >
        

Has the above output.