Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit

Computer Science 131A

Operating Systems

Programming Assignment 1

Jan 2023, Version 3.0

Unix Shell

Description

You will write a simplified version of a Unix shell (the terms shell, command-line interpreter and CLI are used interchangeably in this document.) As you know, the shell is a program that allows users to interact with a computer’s operating system via a set of typed commands.        Windows, Unix and Mac all have shells. Your implementation will be a subset of a simplified Unix Shell.

Purpose

This assignment will help you really understand the concept of a command shell, and the REPL (read-evaluate-print-loop, which will be explained further below). It will also set the stage for   introducing you to concurrent programming (using threads and processes) which is one of the   core concepts and mechanisms of all operating systems.

This assignment may seem daunting, but it is doable by anyone who completed Cosi12b or have other experience with OOP. We have worked hard to make the instructions clear. We will cover this in class and during recitations to make sure you have all the information that you require. I  recommend you review java.io.File,

Make sure you have a good, intuitive grasp of shell commands. Play with the shell. Use pipes    and really understand them. Use the “>” operator and really understand it. Don’t skip this step!

One more thing. Learning your tools is very important as a computer scientist and software       engineer. It sometimes feels like a distraction because, well, I survived fine without it. I suspect many if not most of you (and unfortunately many professional software engineers) debug their code by putting in System.out.println() calls in a thousand places.  This is not a best practice.

PLEASE take the time to learn how to use the Eclipse debugger. I promise you that it will be time well spent, on this assignment as well as subsequent ones, as well as many in your future. Not all programming languages have a good debugger, but Java + Eclipse happens to have a   great one.

The Shell

You can think of the shell as a program that reads a line of input from the user (e.g. the user        types ls readme.txt”) analyzes it, and performs the requested command (which is to locate the   file readme.txt”, open it, and print whatever is in that file.) As you know we refer to this pattern as a read-evaluate-print-loop”, or REPL.

1.   Read: Shell reads input from the user: “cat readme.txt”

2.   Evaluate: Shell parses that into its parts: “cat” and readme.txt”

3.   Print: Shell locates the file and prints out the contents.

Make very sure you grasp the idea of a REPL, why it is called a REPL, and how that abstraction describes what the shell does as it runs.

If we want to implement a shell, we first must know what all the commands are that will be available, what the parameters of each command are, and what it does. A list of commands follows in the next section.

Compound Commands

Shell commands can also be combined on one line to form compound commands” using a “|”   vertical bar also called a pipe symbol. So, for example, one could write “cat readme.txt | wc”,      combining the “cat” command with the “wc” . command.  More generally, user commands can be expressed as a pipeline, which is a convenient abstraction used for the purpose of evaluation.       These pipelines are created by using the pipe operator, defined from left to right.

subcommand1 | subcommand2 | subcommand3

Important notes about implementing a pipeline

A pipeline is implemented as a series of commands, chained together so that the output of each  process is passed into the input of the next. These combinations are a powerful feature of the      Unix shell. In such a pipeline, the output of the one on the left (“cat readme.txt”) fed,       immediately, into the input of the one on the right (“wc”). Because of this capability, some Unix shell commands are often described as “filters” .  You can think of wc” (word count”) filtering  the output of cat” (many lines) into a single line containing the number of words in that file.

To evaluate a compound command, this program will first separate the command into               subcommands represented as Filters. Next, the Filters must be connected with input and output Pipes. Pipes allow Filters to pass lines of data between them. When the filters are executed, they take their input from their input Pipe (if one exists) and write their output to    their output Pipe (if one exists).

Every pipeline must either end in a RedirectFilter or a PrintFilter. If the pipeline command does not specify a redirection file, then a PrintFilter should be automatically appended to a pipeline that should print its output to STDOUT  (i.e. the Eclipse console).

The Unix Shell Commands

In this PA, you will be implementing the following subset of Unix commands.

Command

Description

Pipeline Role

pwd

Returns current working directory.

Cannot have input

ls

Lists the files in the current working directory

Cannot have input

cd <dir>

Changes current working directory to <dir>.

Cannot have input or

output

cat <file>

Reads <file>s contents into STDOUT.

pipe or

Cannot have input

head/tail

Returns up to the first/last 10 lines from piped input.

Must have input

grep

y>

Returns all lines from piped input that contain <query>.

Must have input

wc

Counts the number of lines, words, and

characters in the piped input.

Must have input

uniq

Removes any line from the piped input that is the same as the previous line.

Must have input

cc <r>

Applies a Caesar cipher to the piped input with the provided rotation <r>.

Must have input

exit

Terminates the REPL (already implemented)

Cannot have input or

output

We strongly recommend that you try each of those command on your computer’s own shell (‘terminal’) to make sure you understand what your shell is supposed to do.

Commands can also be combined to form compound commands using the following operators. When a command is part of a compound command, it is also known as a subcommand.

Operator                     Description

[cmd1] | [cmd2]

| chains commands together such that the output of [cmd1] becomes the input of [cmd2]. This is referred to as a pipe.

[cmd1] > <file>

> redirects the output of <cmd1> to be stored in <file>.

Notations used above:

<dir> is a Unix directory specification, e.g., /java/pa1

<file> is a Unix file specification, e.g., /java/pa1/solution.java

<query> a text string indicating the word(s) to match with grep (see below), e.g., “happy

<cmd1> and  are commands exactly as they would appear (e.g., cd /java/pa1)

Terminology:

Parameters are the arguments listed to the right of the subcommands, which define their behavior.

Input is contents delivered to a command by another command, using a pipe.

Output is contents emitted by a command, also using a pipe.

For example, cat requires the parameter, but it does not take any piped input. It does produce output. Any parameters that these commands take are labeled with angle brackets < >   and are required.

Tour of the Code

Main Loop

First, download the zip file and load it into Eclipse. Run the main program, SequentialREPL and you will see the beginnings of your shell. Initially, it only accepts a single command, which is    exit.

Examine the code for the main method of this class. Understand how the “exit” command        works and ends execution of the REPL. Run SequentialREPL and play with it. The REPL runs in the Eclipse Console by nesting a Scanner in a while loop. The user can terminate the REPL by typing the command “exit” which is already implemented for you. The shell will exist in a       particular directory in your file system. This is known as the current working directory. The shell starts in your Eclipse project directory.

SequentialFilter

All your commands (which, recall, are also filters) will be derived from this class. The concept of this class is as follows. (Note: Do not modify SequentialFilter.java. Use it as a base class and derive your command classes from it.)

The constructor will parse” the command and store what information it needs in instance           variables. It doesn’t actually do” anything. You could consider this part of the “Read” step of a REPL. The results of parsing” the command will be stored in instance variables that you define.

The process” method does the actual work of the command. It uses the instance variables       mentioned above and does whatever is required. Any output it generates would come from that method. You could consider this part of the “Evaluate” step of a REPL.

SequentialCommandBuilder

This class manages the parsing and execution of a command. It splits the raw input into separated subcommands, creates subcommand filters, and links them into a list.

You are not required to implement this class, but it is recommended you do. This class has three methods which provide an outline of how you could go about converting a command String into a List of linked SequentialFilters:

createFiltersFromSubCommand: String List

The goal of this method would be to take a String command and produce a List of       SequentialFilters. Hint: Your REPL should use this method to evaluate commands.

constructFilterFromSubCommand: String SequentialFilter

The goal of this method would be to take a String subcommand and return an instance of the   appropriate SequentialFilter. For example, “cat file.txt” would return an instance of CatFilter. Hint: You can use the constructors of your Filters to your advantage.

linkFilters: List → boolean

The goal of this method would be to take a List of SequentialFilter objects                     corresponding to subcommands and link them together. To understand what linking means, make sure to read SequentialFilter.

Hint: Some errors can be caught in constructFilterFromSubCommand while others can be caught in this method. Correctly deciding which errors should be caught at this stage will lead to an elegant solution.

Error Handling

The shell should not crash when an error is encountered, and instead display an appropriate error message. Each error message can be found in the Message enum and must be called from there. The shell must detect the following errors and display the appropriate message:

Message                                                    Trigger

COMMAND_NOT_FOUND

Executing an unlisted command.

FILE_NOT_FOUND

Reading from a file that does not exist

DIRECTORY_NOT_FOUND

Changing current directory to one that does not exist

REQUIRES_PARAMETER

Failing to pass a parameter to a command that requires one

REQUIRES_INPUT

Failing to pipe into a command that requires input

CANNOT_HAVE_INPUT

Piping into a command that does not take input

CANNOT_HAVE_OUTPUT

Piping from a command that does produce output

Note: Given the descriptions of commands and errors, you may assume that your commands will not be provided too many parameters. That is, a command that receives no parameters (like pwd) will not be provided with any and a command that receives 1 parameter (like grep) will not be   provided with more than one.

For this reason, you can parse a command with a parameter as: [command] [space] [parameter].

Implementing a command (e.g., cat)

Let’s walk through how you would go about implanting a simple command, e.g., cat.

1.   Make sure you know what the command does in your actual shell. Try it alone and as part of a pipeline.

2.   Look at the required definition of the command in this document. Determine whether the command takes any parameters. Determine its role in a pipeline, that is, does it take an    input pipe, an output pipe, both, or neither. Make sure you understand.

3.   Create a new subclass of SequentialFilter that will implement the command, e.g.,            CatFilter. The constructor “parses” the command and the process method “executes” the command. Recall from above, the result of parsing” (which is different for each            command) is stored in instance variables of your CatFilter. The process method uses      those instance variables to do what it needs to dl.

4.   Update SequentialCommandBuilder to recognize the cat command, at which point it creates a new instance of CatFilter.

Subcommand Details

The current working directory of the shell can be retrieved using the get () method of the       filter package’s CurrentWorkingDirectory class. You can update the shell’s current working directory to be a new String using CurrentWorkingDirectory.setTo().

See the file for more details. The cd subcommand must support the special directories “ .” (the current directory) and “ ..” (the parent directory). It must allow for chaining of directory          movement. For example cd dir1/dir2/dir3.

The wc subcommand counts the number of lines, words and characters in the piped input, and   outputs them as a string separated by spaces. A word is a sequence of characters not separated by a space. For example, an input with two lines “I am”, “input. ” has 3 words “I”,  “am”,    and “input. ”, 2 lines, and 10 characters. This should be output as “2 3 10”.