, , ,

[SOLVED] Cs537 p3: unix shell

$25

File Name: Cs537_p3__unix_shell.zip
File Size: 188.4 KB

Categories: , , , Tags: , , ,
5/5 - (1 vote)
5/5 – (1 vote)

Overview:

In this project, you will implement a simple UNIX shell similar to bash and sh. The shells you implement will be similar to, but much simpler, than the one you run every day in Unix. You can find out which shell you are running by typing echo $SHELL at a prompt. You may then wish to look at the man pages for sh or the shell you are running to learn more about all of the functionality that can be present. For this project, you do not need to implement as much functionality as is in most shells. Your shell will implement the following features:

  1. Different modes of execution: Interactive mode and batch mode of execution.
  2. Aliases: Aliases are mainly used for abbreviating commands, or for adding default arguments to a regularly used command.
  3. Output redirection: When specified, the output should be written to a file instead of stdout.
  4. Environment variables: Environment variables are special shell variables that are used by applications executed by the shell.

Last, your shell will implement some built-in commands such as aliasexportunset to support the above features.

Learning objectives:

  1. Gain more familiarity with programming in C and in Linux
  2. Learn how processes are created, managed and terminated
  3. Gain exposure to some of the functionality in shells

Deliverables:

  • .c file named wisc-shell.c
  • The file should compile successfully when compiled with the -Wall and -Werror flags.
  • The shell should pass tests we supply as well as tests that meet our specification that we do not supply
  • Include a README.md describing your implementation

Administrivia:

  • This project is to be performed alone.
  • Due Date: June 24th, at 11:59pm (4 slip days throughout course, use wisely)
  • Similar to P1, a small portion of the credit is allocated for good programming style and memory management. Read the detailsLinks to an external site..
  • This project is to be done on the lab machinesLinks to an external site., so you can learn more about programming in C on a typical UNIX-based platform (Linux).

Details:

In this assignment, you will implement a UNIX shell called wish (WIsconsin SHell). The shell should operate in this basic way: when you type in a command (in response to its prompt), the shell creates a child process that executes the command you entered. After the child process finishes, the shell then prompts for more user input.  More specifically, shells are typically implemented as a simple loop that waits for input and fork()s a new child process to execute the command; the child process then exec()s the specified command while the parent process wait()s for the child to finish before continuing with the next iteration of the loop.

1. Different modes of execution

Your shell can be run in two modes: interactive and batch.   The mode is determined when your shell is started.

  1. If your shell is started with no arguments (i.e., ./wish) , it will run in interactive mode. In interactive mode, you will display a prompt (the string wish> , note the space AFTER the > character, to stdout) and the user will type in a command at the prompt.
  2. If your shell is given the name of a file (e.g., ./wish batch-file), it runs in batch mode. In batch mode, your shell is started by specifying a batch file on its command line; the batch file contains the list of commands (each on its own line) that should be executed. In batch mode, you should not display a prompt. In batch mode, you should echo each line you read from the batch file back to the user (stdout) before executing it; this will help you when you debug your shells (and us when we test your shell — most of which will be of batch mode). If the line is empty or only composed of whitespace, you should still echo it.

In both interactive and batch mode, your shell terminates when it sees the exit command on a line or reaches the end of the input batch file.

  • Assume that the max length of any command including its arguments is 512 bytes.
  • If any of the input commands is invalid, print Error: command not found
     to stderr but continue processing next set of commands.
  • If there was any error while opening the batch file, print Error: could not open batchfile
     to stderr.

 

2. Aliases

Most shells contain functionality for aliases. To see the aliases that are currently active in your Linux shell, you can type alias. Basically, an alias is a short-cut so that users can type in something simple and have something more complex (or more safe) be executed.

For example, you could set up:

wish> alias ll /bin/ls -al

so that within this shell session, the user can simply type ll and ls -al will be run.

There are three ways that alias command can be invoked in your shell.

  1. Creating new aliases: To create new aliases, the user types the word alias, followed by a single word (the name of the alias), followed by a replacement string(s). you should set up an alias between the alias-name and the value (e.g. alias ll ls -la). If the alias-name was already being used, just replace the old value with the new value.
  2. Displaying existing aliases: If the user types a single word alias (no other following words), your shell should print all the aliases that have been set up so far with one per line. The format is alias name=replacement string. Print the aliases in the order they were created. Example:
    wish> alias ll ls -al
    wish> alias grep grep --color=auto
    wish> alias # Print all aliases
    ll=ls -al
    grep=grep --color=auto
  3. Displaying specific alias: If the user types alias followed by a word, if the word matches a current alias-name, print the alias-name, followed by ‘=’ and corresponding replacement string within single quotes (‘ ‘); if the word does not match a current alias-name, print Error: alias not found
     and continue. Example:

    wish> alias ll # Display ll alias
    ll='ls -al'

To use an alias after it is created, the user types the alias just as they would type any other command:

wish> alias ll /bin/ls -l # Create the alias
wish> ll # Run the alias command
  • You should be able to handle an arbitrary number of aliases.
  • You do not need to worry about aliases to other aliases, aliases that involve redirection, or redirection of aliases.
  • Running an alias with additional arguments (e.g. ll -a where ll is an alias-name) is undefined behavior. We will not test this. All alias calls will consist of only the alias-name.
  • If an alias with an invalid command is executed, then print Error: command not found
    .

 

3. Environment variables

Environment variables are user-definable values that are used by applications. Environment variables are part of the environment in which a process runs. In this case, the environment is that of your shell. In Linux, every process has its set of environment variables. These variables are stored in the environ extern variable. Some important things about environment are the following:

  • When fork is called, the child process gets a copy of the environ variable.
  • When a system call from the exec family of calls is used, the new process is either given the environ variable as its environment or a user specified environment depending on the exact system call used. See man 3 exec.
  • There are functions such as getenv and setenv that allow you to view and change the environment of the current process. See the man environ for more details.

Your shell will support the setting, unsetting and displaying environment variables through built-in commands:

  1. Setting environment variables: To add environment variables, users will use the export command. Example: export MYENVVARNAME=somevalue. After this command is executed, the MYENVVARNAME variable will be present in the environment of any child processes spawned by the shell.
  2. Unsetting environment variables: Environment variables can be removed using the unset command. Example: unset MYENVVARNAME. After this command is executed, MYENVVARNAME variable is removed from the environment from all future child processes spawned by the shell.
  3. Variable substitution: Whenever the $ sign is used at the start of a token, it is always followed by a variable name. Variable values should be directly substituted for their names when the shell interprets the command. Tokens in our shell are always separated by whitespace, and variable names and values are guaranteed to each be a single token. For example, given the command mv $ab $cd,, you would need to replace variables ab and cd. Please note that, for a token to be considered a variable name, the token must start with the $ sign which means in a command like cd ab$ef, the token ab$ef is a normal token, it is not a variable name, our shell must not attempt to substitute $ef.
  4. Displaying environment variables: The env utility program (not a shell built-in) can be used to print the environment variables. You don’t have to implement any command to print environment variables.

You can assume the following about environment variable handling for your shell:

  • There will be at most one variable assignment per line.
  • Lines containing variable assignments will not include any other commands or arguments.
  • The entire value of the variable will be present on the same line, following the = operator. There will not be multi-line values; you do not need to worry about quotation marks surrounding the value.
  • Variable names and values will not contain spaces or = characters.
  • There is no limit on the number of variables you should be able to assign.
  • There won’t be recursive variable definitions of the form local anothervar=$previousvar.
  • If a non-existent environment variable name is passed to unset, print unset: environment variable not present
    .
  • unset can be called with multiple arguments. For example, unset ENVVAR1 ENVVAR2. Your shell should unset all the environment variables passed to unset. If one or more of the variables does not exist, unset the rest and print unset: environment variable not present
    .

 

4. Output redirection

Many times, a shell user prefers to send the output of a command to a file rather than to the screen. Usually, a shell redirects standout output to a file with the > character; your shell should include this feature.

For example, if a user types /bin/ls -la > ls.out into your shell, nothing should be printed on the screen.  Instead, the standard output of the ls program should be rerouted to the file ls.out. Note that stderr output should not be changed; it should continue to print to the screen. If the output file exists before you run your program, you should simply overwrite it (after truncating it, which sets the file’s size to zero bytes).

The exact format of redirection is: a command (along with its arguments, if present), followed by any number of white spaces, the redirection symbol >,  again any number of white space, followed by a filename.

  • Multiple redirection operators (e.g. /bin/ls > > file.txt ), starting with a redirection sign (e.g. > file.txt ), multiple files to the right of the redirection sign (e.g. /bin/ls > file1.txt file2.txt ), or not specifying an output file (e.g. /bin/ls > ) are all errors. Print Redirection error
     to stderr.
  • If the output file cannot be opened for some reason (e.g., the user doesn’t have write permission or the name is an existing directory), your shell should print exactly Redirection error
    .  In these cases, do not execute the command and continue to the next line.
  • Do not worry about redirection for built-in commands (aliasunaliasexportunset and exit); we will not test these cases.

 

Built-in commands:

A built-in command means that the shell interprets this command directly; the shell does not exec() the built-in command and run it as a separate process; instead, the built-in command impacts how the shell itself runs. You need to write code that handles these commands. You will be implementing the following commands which have been described above:

  1. alias: Used to create new aliases or display existing aliases.
  2. export: Used to assign environment variables.
  3. unset: Used to delete environment variables.
  4. exit: Used to end the shell.

Hints

This project is not as hard as it may seem at first reading; in fact, the code you write will be much, much smaller than this specification. Writing your shell in a simple manner is a matter of finding the relevant library routines and calling them properly.

Your shell is basically a loop: it repeatedly prints a prompt (if in interactive mode), parses the input, executes the command specified on that line of input, and waits for the command to finish, if it is in the foreground. This is repeated until the user types “exit” or ends their input.

You should structure your shell such that it creates a new process for each new command. There are two advantages of creating a new process. First, it protects the main shell process from any errors that occur in the new command. Second, it allows easy concurrency; that is, multiple commands can be started and allowed to execute simultaneously.

Below, we suggest some routines that you could use in your implementation:

  • Parsing input commands: For reading lines of input, you may want to look at fgets(). You may find the strtok() routine useful for parsing the command line (i.e., for extracting the arguments within a command separated by spaces). A warning about strtok(): It modifies the input string, and the char pointers it returns point to various indices within the input string. You may find strdup() handy if you want to preserve the input string, but be careful about memory leaks.
  • Writing to stdout or file: Make sure you use the write() system call for all printing (including prompts, error messages, and job status), whether to stdout or to stderr. Why should you use write() instead of fprintf() or printf()? The main difference between the two is that write() performs its output immediately whereas fprintf() buffers the output temporarily in memory before flushing it. As a result, if you use fprintf() you will probably see output from your shell intermingled in unexpected ways with output from the jobs you fork(); you will fail our tests if your output is intermingled. If you decide to use fprintf() make sure you ALWAYS call fflush() immediately after the call to fprintf().
  • Executing commands: Look into fork()execvp(), and waitpid().The fork system call creates a new process. After this point, two processes will be executing within your code. You will be able to differentiate the child from the parent by looking at the return value of fork; the child sees a 0, the parent sees the pid of the child.

    You will note that there are a variety of commands in the exec family; for this project, you must use execvp. Remember that if execvp is successful, it will not return; if it does return, there was an error (e.g., the command does not exist). The most challenging part is getting the arguments correctly specified. The first argument specifies the program that should be executed, with the full path specified; this is straight-forward. The second argument, char *argv[] matches those that the program sees in its function prototype:

    int main(int argc, char *argv[]);

    Note that this argument is an array of strings, or an array of pointers to characters. For example, if you invoke a program with:

    /bin/foo 205 535

    then argv[0] = “/bin/foo”, argv[1] = “205” and argv[2] = “535” (where is string is NULL terminated). Note the list of arguments must also be terminated with a NULL pointer; that is, argv[3] = NULL. We strongly recommend that you carefully check that you are constructing this array correctly!

    The waitpid system call allows the parent process to wait for one of its children.

  • Setting/unsetting environment variables: You could use the setenv() and unsetenv() routines to manage environment variables. Refer to their man pages for more information. Similarly, for retreating the value of an environment variable, you can use the getenv() routine.

Testing and Handing in your code

  • Tests will be provided at ~cs537-1/tests/p3. You can read more about the tests in the README in that directory.
  • Handing it in: Copy your file to ~cs537-1/handin/login/p3 where login is your CS login. Do NOT use this handin directory for your work space.  You should keep a separate copy of your project files in your own home directory and then simply copy the relevant files to this handin directory when you are done.  The permissions to this handin directory will be turned off promptly when the deadline passes and you will no longer be able to modify files in that directory.
Shopping Cart

No products in the cart.

No products in the cart.

[SOLVED] Cs537 p3: unix shell
$25