In this MP, you will implement a simple Unix shell interpreter (e.g., bash, csh etc.) called Shell. The basic function of a shell is to accept commands as inputs and execute the corresponding programs in response.
The purpose of this MP is to help you learn the basics of system calls for creating and managing processes as you implement your Shell program. You will write the code for your Shell program in shell.c inside your mp3 directory. The two other files you will write inside your mp3 directory: log.c and log.h will provide the implementation of a generic data structure log_t that you will use to implement the history feature of your Shell.
You will implement the data structure: log_t. The structure is defined inside log.h and the functions supported should be implemented inside log.c. You should use log_t to store the command history of your Shell. To use log_t, just include the header file "log.h" inside your Shell program shell.c. The API for the data structure as well as function descriptions can be found at log.html
To help with testing the log_t data structure, we provide you with the shell of a test program in testlog.c. Feel free to modify this tester file. To make and run the tester for the data structure testlog.c, use:
%> make # make/compile your program
%> ./testlog # run the testlog program
Your shell prompt MUST use the following format:
(pid=x)/path/to/cwd$You can use printf("(pid=%d)%s$ ", pid, cwd) to print out this prompt. Here pid is the current process ID and cwd is the current working directory. Use getpid() to determine the process ID of the current process and getcwd() to lookup the current working directory.
Read a line from standard input. This line will be your command. The function getline() can be used to accomplish this easily. Beware that getline() may allocate memory that the user must free.
Shell supports two types of commands: built-in and non built-in. While built-in commands are executed without creating a new process, a non built-in command MUST create a new process to execute the program for that particular command.
In order to decipher the command it will be necessary to tokenize the input on spaces. A token is any non-space character surrounded by the start or end of the line or spaces. The first token is considered the command and all subsequent tokens are referred to as the arguments. The strtok() function will help tokenize the command for you. Make sure to read the strtok()'s man pages to understand its nuances.
Your Shell will support four built-in commands: cd, exit and two commands (!# and !) based on a history feature. You should use the log from Part 1 to implement the history functionality.
cd xxx
Changes the Shell's current working directory to 'xxx'. [Hint: Relevant function chdir().]
If for some reason (for example, xxx is not a valid directory) the command "cd" does not end successfully, Shell should retain its current working directory and should print (without the quotes): "xxx: No such file or directory\n" to indicate the error, where xxx is the first argument. Note that only the first argument is used, all others are ignored.
exit
Terminates the Shell. All arguments are ignored.
!#
Prints a list of all the commands saved in the history separated by newlines. Oldest commands should print first. Do not store !# in the history. Ignore all arguments. For example:
ls -l
pwd
ps
!query
Re-executes the last command whose prefix matches query. Query can be any combination of characters. Print the match with printf("%s matches %s\n", query, match) before executing the command. If no match is found print "No Match". Re-executed commands should be stored in the history. Nothing will be stored in the history if no match is made. For example:
ls -l
ls
!l (re-executes and stores ls)
!ls - (re-executes and stores ls -l)
!d (no match, does nothing)
If the command is not a Shell built-in (i.e., any command other than cd, exit, !# and !query), Shell should consider the command name to be the name of a file that contains executable binary code. Such a code must be executed in a process different from the one executing the shell.
Follow the following steps to execute a non built-in command:
Some non built-in UNIX commands that you may try to see whether your Shell works as it should are: ls, pwd, ps, echo hello.
To compile and run your shell, run the following commands from a Terminal on a Linux machine:
%> make
%> ./shell
%> ./gdb-shell
%> ./valgrind-shell
Type "exit" to exit from Shell or if this has not yet been implemented Ctrl-C to send a SIGINT signal. In order to run with gdb or valgrind use ./gdb-shell and valgrind-shell respectively.
An example session of Shell can be found here to help illustrate the built-in functions. Example
If you work around the script we have provided AND fork bomb your machine, you will be shown no pity.
Please fully read mp_grading.html for more details on grading, submission, and other topics that are shared between all MPs in CS 241.