CSc 422 Assignment 2

Shell We Run?


Sep 25
75 pts
Oct 9

The Unix shell program is an ordinary user-mode program which reads lines of text, breaks them into words, and runs the result. The first word is treated as the name of a command, and the list of words becomes the array of strings passed to main as argc and argv. This assignment is to create a simple shell. When working, your program would run something like this:

[tom@tomslap minishell]$ ./pgm cmd> ls Makefile boom.cpp die1 lbtest.o pgm startmk~ base boom.cpp~ die1.cpp linebreaker.cpp pgm.cpp base.cpp complete lbtest linebreaker.h pgm.cpp~ base.o complete.cpp lbtest.cpp linebreaker.o pgm.o boom complete.o lbtest.cpp~ mini1b.cpp startmk cmd> ls -l pgm pgm.cpp -rwxrwxr-x. 1 tom tom 26648 Sep 17 22:52 pgm -rw-rw-r--. 1 tom tom 149 Sep 17 22:22 pgm.cpp cmd> ls Makefile missing ls: cannot access 'missing': No such file or directory Makefile * Process 57845 exited with code 2 cmd> ./boom * Process 57846 crashed: Segmentation fault cmd> exit [tom@tomslap minishell]$

Your program will execute a simple loop, whose body does this:

  1. Issues a prompt and read one line of text.
  2. Break that line into words.
  3. If the command is a built-in, the program handles it, then reads the next line. Otherwise,
  4. Executes a fork to create a new process. Report the cause of any failure (and, of course, don't exec if the fork failed).
  5. The child process treats the first word in the list as the name of a command and runs it using an exec call. It sends the whole list as the parameters to the program. Report the cause of any failure.
  6. The parent process waits for the child process to exit, then reports its status.
  7. Back to the read for the next command.

Your program will have to read a line of input and break it into words. You are given a C++ LineBreaker class to help with that, and an example program that shows how to use it. A LineBreaker object is constructed from a string, then behaves as an array of the words in that string. The execution example from the notes shows how to use fork() and exec() to run another program. The LineBreaker also shows how to send the resulting array of strings to exec, which the runner example does not. You will want to swipe code from both these examples.

Here's what you need to do. You may want to swipe code from both the fork/exec example, and from the line breaker example.

Built-in Commands

The two built-in commands are exit and cd, which the shell must perform itself. After parsing the command, you need to check if breaker[0] is equal to one of those commands, and treat it as a special case. In the case of exit, a process can't very well let a another process terminate for it, so it must do this itself. Simply make the exit call. If the command has an argument, it should be a number which you send as the argument to the exit call. Otherwise, run exit(0). You will need that parameter as an integer. The line breaker has a method for this, or you can use atoi.

Processes have a current directory, which is used as location for simple file names. The cd command changes the current directory for the shell process. Since the current directory is a process attribute, it must be done by the shell itself. (If that's not obvious, think about it a minute.) Use the chdir call. If the cd command has an argument, use it as the directory to change to. If not, go to the home directory. You can find out the home directory with the call getenv("HOME") (see getenv). Recall that either the getenv or chdir calls may fail; be sure to report any failure.

Termination Status

When an external command finishes and the parent wait returns, you should report how the process ended. If it exited with a zero exit code (normal exit), don't print anything. If it exited with a non-zero exit code, report the code. If it crashed, report the cause of the crash. “Crash” just means it was terminated with a Unix signal. The wait call is used to wait for the child process to finish report the cause of termination to the parent. The execution example shows how to capture the termination status, and tell if the process exited or crashed, using WIFEXITED or WIFSIGNALED. Read the manual page for descriptions WEXITSTATUS to find the exit code, and WTERMSIG to discover the cause of a crash. You will also want strsignal to convert the signal number into readable message.

Start File

If you like, you may download, which is an archive containing the line breaker class and its tester, along with a make file and mostly empty file called main.cpp. You can get started on Sandbox (or another Unixish system) like this:
[tom@localhost tmp]$ wget --2023-09-23 17:46:44-- Resolving ( Connecting to (||:80... connected. HTTP request sent, awaiting response... 200 OK Length: 3033 (3.0K) [application/zip] Saving to: '' 100%[===================>] 2.96K --.-KB/s in 0s 2023-09-23 17:46:44 (41.3 MB/s) - '' saved [3033/3033] [tom@localhost tmp]$ mkdir a2start [tom@localhost tmp]$ cd a2start [tom@localhost a2start]$ unzip ../ Archive: ../ inflating: linebreaker.h inflating: linebreaker.cpp inflating: lbtest.cpp inflating: main.cpp inflating: Makefile [tom@localhost a2start]$ make g++ -c -o main.o main.cpp g++ -c -o linebreaker.o linebreaker.cpp c++ main.o linebreaker.o -o main g++ -c -o lbtest.o lbtest.cpp c++ lbtest.o linebreaker.o -o lbtest [tom@localhost a2start]$ ./main This program is not finished yet.
You can then write your program in main.cpp and build it with make.

The starting code and my solution program compile and run fine on a BSD kernel, so I would expect no trouble running on a Mac. You may not have wget or zip commands installed, but saving with a browser and/or unpacking it with a GUI tool should work just fine.


When your program works correctly and looks nice, submit it here. There's a place there to send the linebreaker, but don't bother unless you changed it.