All files for this assignment are to have version numbers in the range 2.1 through 2.x. Check out each file from Assignment 3 for editing and, without making any changes, check it in again as version 2.1. For example, you would use the following commands for Makefile:
% co -l Makefile % ci -f -r2.1 Makefile > No changes. Starting work on Assignment 4. . %The -r option specifies the revision number you want to use, and the -f option forces the check in to succeed even though the file hasn't been changed. Normally, ci won't let you check in a file if you haven't actually changed anything in it.
Future assignments will use version numbers 3.x, 4.x, etc.
The main() method is to be defined in a file named ouch.cc. For this assignment you will be coding the following methods, each of which is to be in its own .cc file:
|
int getCommandLine( char *buf, int max );If the value of the environment variable "PS1" is set to anything, use that as the prompt string to display before reading the command line from the user. If PS1 is not set, print "no-prompt> " as the prompt string.
Use fgets() to read a command line from the user into .buf If a command line ends with a backslash (\), read another command line and append it to whatever you have read so far. When reading continuation lines, use the value of "PS2" as the prompt string if it is set, and use the string "no-prompt>> " if PS2 is not set. Never store more than max bytes into buf, including the end-of-string '\0'.
This function returns 0 if it is successful. It should never fail, but would return -1 if it did.
subcommand_t* getSubcommands( char *buf );This function receives a complete command line (the one returned by getCommandLine()), and returns a pointer to the head of a linked list of structures. The structure type includes a char* pointing to a complete command, and a char that contains the character that terminated the command, defined like this:
struct subcommand_t { char *cmdString; char termChar; token_t *tokens; subcommand_t *next; };The tokens field of this structure will be filled in with the result of a subsequent call to tokenize().
The terminating character will be removed from the command string by this function, and returned in the termChar field of the subcommand_t structure. Possible terminating characters are ';', '&', '|', '\0', and possibly other characters to be added in future versions of the shell. (For example, ksh lets you type a subcommand inside parentheses, which makes the command run in a "subshell." The terminating character would be ')' in that case.)
You need to code this function so it does not get fooled by terminating characters that appear inside strings or which are escaped by backslashes. For example, the following is all one subcommand, terminated by '\0':
echo 'Semicolons (;) & pipes (|)' \& " ||| " are nice.There are three quote characters this function should recognize: single ('), double ("), and backquote (`). The function does not have to recognize nested quotes, like "The character '!' is nice."
The memory for the linked list will have to be allocated dynamically, but the cmdString pointers may be pointers into the buf parameter. That is, this function may modify buf, much the way strtok() modifies the strings it tokenizes.
token_t *tokenize( const subcommand_t *cmd );This function returns the head of a linked list of token_t structures defined as follows:
struct token_t { char *token; token_tag_t tag; token_t *next; };The tag for each token with be taken from the following list of enumerated values:
|
This function does not modify the string passed to it because the character that terminates one token might be the beginning of the next one, so there is no easy way to instert '\0' characters to break up the original string. Rather, the memory for the tokens has to be allocated dynamically.
Note that strings enclose tokens. The echo command listed above contains six tokens:
Although you can't use this code directly, the function [ strtok2() ] might give you some ideas how to work on the design of this function.
char *expandToken( const char *token )This function is called by the main program for each token that is of type T_default or T_double_quote. If the token is the string "$?", this function substitutes a string representing the previous command's result code for the token. Otherwise, this function returns the same pointer that was passed to it. Since tokens are allocated memory dynamically, this function must free the pointer passed to it if it does perform a substitution.
int executeCommand( subcommand_t *cmd );This function executes a single command, which might be either a builtin command or an external command. It returns the "result code" for the command, which is the exit code for external commands, or the the return code for builtin commands. If the command is neither an external command nor a builtin command, this function writes an error message to stderr and returns the value 1.
int result; char inBuf[ 1024 ]; while ( true ) { // Read a command line result = getCommandLine( inBuf, sizeof( inBuf ) ); if ( result != 0 ) { fprintf( stderr, "Error reading command line\n" ); continue; } fprintf ( stderr, "Result: %d\nStrlen: %d\nCommand: %s\n", result, strlen( inBuf ), inBuf ); // Break line into subcommands and tokenize each one subcommand_t *cmd_list = getSubcommands( inBuf ); if ( 0 == cmd_list ) continue; // Empty command line subcommand_t *cmd = cmd_list; while ( cmd ) { token_t *token_list = tokenize( cmd ); if ( 0 == token_list ) continue; // Not empty, but no tokens cmd->tokens = token_list; // execute it exitCode = executeCommand( cmd ); cmd = cmd->next; } }
The main purpose of Version 2.x of the assignment is to be sure your project is set up with the proper source modules and Makefile. Be sure "make clean" leaves your project directory with nothing but the RCS subdirectory in it, be sure that "make" builds program correctly after you run "make clean,", be sure "make" recompiles only those files you have edited since the last make, and be sure that the sequence "touch ouch.h ; make" recompiles everything before linking.
Be sure to test your program carefully to be sure it works. In particular, be sure nothing happens in interactive mode if the user types an empty command line. Be sure prompt strings are handled correctly; you will probably find that the PS1 and PS2 variables are already set up if you log into a Linux account, and the prompt printed for PS1 will not be "pretty." But on forbin you will have to set up your own values for PS1 and PS2.
The assignment will be graded on a 5-point scale, with each of the following counting approximately equally: