UNIX and C Programming

Assignment 3

Deliverables and Due Date

Put all source files for this project in a tar file, and email the zip file to me by midnight of the date of the final exam, May 20.

Be sure to put your name and ID number in the body of your email message. Also tell me any optional parts of the assignment that you have completed in your email message.


At this point, you have code which parses command lines into individual commands. You have also written code that uses a dispatch table to process built-in commands. The remainder of the project is to build a complete shell, building on your existing code. The Development Steps outlined below will lead you through this process.

Development Steps

  1. Parse Individual Commands. Use your strtok() from Assignment 2 to tokenize individual commands. Tokens are separated by space, tab, <, and > characters. Print out each token for each command to verify that this step works correctly.

    Optional: Recognize all I/O redirection operators as separate tokens.

    Optional: (hard) Treat quoted strings as single tokens. This can include single quotes and backslashes as well as double quotes.

  2. Build Argument List. Before determining whether a command is built-in or external, construct an argument list for the command based on the tokens entered by the user. You may impose a limit of 10 on the number of command line arguments a user may enter, but in this case, if there are more than 10, you must issue a warning message saying that additional arguments are being ignored. Do not include I/O redirection symbols or their arguments in the list of command-line arguments.

    Optional: Use malloc() to allocate memory for the pointers in the argument list dynamically instead of putting a fixed limit on the number of allowed arguments. Don't forget to free (free()) each list.

    Optional: Put all the information about each command, including I/O redirection information. into a struct like this one:

        struct cmd_info {
          char   *cmd_name;
          char   *argv[10];
          char   *stdin[2];
          char   *stdout[2];
          char   *stderr[2];
          char  **envp;
    Whether you build the complete structure or just the argument list, be sure it is correct either by printing it out or by examining it using gdb.

  3. Process Built-in Commands. Use the dispatch table developed [ earlier ] to process the first token in each command. Modify the prototype for built-in commands to match the one for main(). When you have this working, you should be able to enter the following command line: "ls; exit; date" and the program should report that ls is not a built-in command, then exit.

  4. Recognize External Commands. If a command name is not in your list of built-in commands, look for it in each directory listed in the PATH environment variable. If found, print the complete pathname of the file. If not found, print an error message indicating that the file cannot be found.

  5. Execute External Commands. Fork a process to execute each external command, and execute it. If the command exits with a non-zero exit code, print its value.

  6. Set Shell Variables. PS1 and PATH should have immediate visible effects, but all variables should be saved. Modify the environment passed to a command based on the settings of these variables.

  7. Set. Implement the set built-in command.
  8. Aliases. Implement the alias built-in command.
  9. Variable Substitution. For each reference to a shell variable in a command line, substitute the value of the corresponding shell variable before tokenizing the command.

  10. Pattern Substitution. It should be obvious to you how to do this.

  11. Control Structures. Implement case, if, and for. For full credit, you need to extend your shell to implement at least one control structure not already available in the KornShell.