James Bush

| about | posts | projects | contact | etc

A Unix Shell in C

Mar. 1, 2023

For CS344, I wrote a Unix shell program for a mid-term assignment. The shell is written in C and can be compiled with GCC. Per the assignment requirements, the program needed to do the following:

  1. Print an interactive prompt to input
  2. Tokenize input
  3. Implement parameter expansion ($$, $?, $!, ~)
  4. Implement exit and cd
  5. Execute non-built-in commands using the appropriate EXEC(3) function
  6. Implement redirection operators < and >
  7. Implement the & operator to run background commands
  8. Implement SIGINT and SIGSTP signals

Project Breakdown:

As a relative newcomer to programming in C, this project was a big challenge. Success in this project really demanded that the requirements be broken down into manageable sub-tasks. The order of building and integrating each of the component parts became important. We had two weeks (though, with overlapping deadlines for two other projects) to complete the assignment and I started as early as it was released and worked every day until the day before the deadline to complete it. Below, I discuss the major component parts of the project in the general order I constructed them to complete the assignment. The project can be viewed on GitHub here: https://github.com/bushjam1/cs344_smallsh


Our class was required to build the project using SSH on a RHEL server maintained by the college. Our instructor asked that we use Neovim, for which we were provided a Bash script that implemented basic features like autocomplete. We were required to compile the program with GCC and allowed to use GDB for debugging. A script was also provided that functioned as a grading script. The score we recieved on the grading script was the grade we received for the project. The grading script was useful for taking a sort of test-driven development approch, which most of the class used.

1. Input

Before printing an input prompt to the command line, our program needed to check for any background processes in the same process group ID as the program and print an error message to stderr for exited or signaled processes. For the input prompt, we were required to do parameter expansion and print the PS1 parameter $ for an input prompt. After printing the input prompt, the program is required to read from stdin.

2. Word Splitting and Tokenization

Once input is obtained from stdin, I performed word splitting / tokenization with a function, split_words(), that tokenized the input string in a loop with inbuilt method, strtok(). Each actionable, tokenized word is placed in a word array and then passed to the function parse_words() to determine the series of commands and options to run.

3. Expansion

As mentioned above, string expansion is performed by the expand_word() function, which essentially takes several key words and converts them into more meaningful strings for execution of various tasks. These include the tilde, ~ for the home directory, $$ for the foreground process ID, $? for the last foreground process exit status, and $! for the PID of the most recent background process. The relevant expansion is then returned as a character array to pass to parse_words().

4. Parsing

The parsing logic implemented in parse_words() is the final step before specific commands and options are run. It serves several purposes. First, it detects comments indicated by the hashmark #. It also detects whether a process is indicated to run as a background process by the presence of an amperstand & at the end of a command. It will then detect command words and run them as necessary, or return to the command prompt if no commands are detected.

5. Execution

The execution function, execute_commands(), performs the bulk of logical operations in SMALLSH program. It first checks for the presence of any invocation of cd or exit and will run those with the cd_smallsh() or exit_smallsh() functions. If neither cd or exit are invoked, execute_commands() will then pass the word/commands to run as a new child process by using FORK(2).

6. Built-In Functions


7. Signals