A Simple SHELL Program

Posted on by on May 1st, 2013 | 0 Comments ยป
////////////////////////////////////////////////////////////////////////////////
/**
 *  @file myshell.c
 *  @brief A C program to implement an interactive shell in which a user
 *  executes commands.
 *
 *  @title My Shell
 *  @author Nicholas Guthrie
 *  @web http//nickguthrie.com
 *  @created February 26, 2013
 *
 *  Compile with: gcc -Wall -g -o ./myshell myshell.c
 *  Flags: -DVERBOSE for verbose child/parent status outputs
 *         -DCMD to print the command struct after it has been parsed
 *         -DPRINTCD to print the current directory after every command
 *         -DTOKEN to print the token parsing
 *
 */
////////////////////////////////////////////////////////////////////////////////
/**----------------------------------------------------------------- :Libraries:
//                      _    _ _                 _
//                     | |  (_) |__ _ _ __ _ _ _(_)___ ___
//                     | |__| | '_ \ '_/ _` | '_| / -_|_-<
//                     |____|_|_.__/_| \__,_|_| |_\___/__/
//
//----------------------------------------------------------------------------*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>  //for umask
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
/**-------------------------------------------------------------------- :Global:
//                            ___ _     _          _
//                           / __| |___| |__  __ _| |
//                          | (_ | / _ \ '_ \/ _` | |
//                           \___|_\___/_.__/\__,_|_|
//
//----------------------------------------------------------------------------*/
#define BUFFER_LENGTH 100        /* maximum buffer length */
#define MAX_ARGS 64              /* maximum number of arguments */
#define MAX_ARG_LEN 100          /* maximum length of a single argument */
#define WHITESPACE " ,\t\n"      /* whitespace in commandline args */
#define MAX_HISTORY 999          /* maximum commands to be stored in history */
#define PATH_VAR "MYPATH"        /* the environment path variable */
/**------------------------------------------------------------------- :Structs:
//                          ___ _               _
//                         / __| |_ _ _ _  _ __| |_ ___
//                         \__ \  _| '_| || / _|  _(_-<
//                         |___/\__|_|  \_,_\__|\__/__/
//
//----------------------------------------------------------------------------*/
struct command_t {
    char *name; /* redundently stored in argv[0] */
    int background;
    int argc;
    char *argv[MAX_ARGS];
    int hasOutput, hasInput, specialOutput;
    char *input;
    char *output;
};
/**---------------------------------------------------------------- :Prototypes:
//                  ___         _       _
//                 | _ \_ _ ___| |_ ___| |_ _  _ _ __  ___ ___
//                 |  _/ '_/ _ \  _/ _ \  _| || | '_ \/ -_|_-<
//                 |_| |_| \___/\__\___/\__|\_, | .__/\___/__/
//                                          |__/|_|
//
//----------------------------------------------------------------------------*/
int parsePath( char *dirs[] );
int getPath (char *dirs[], int dir_size, char * command, char * path_out);
int parseCommand(char *cLine, struct command_t *cmd);
void printCommand(struct command_t *cmd);
int storeHistory (char * cLine, char history[MAX_HISTORY][BUFFER_LENGTH],
		  int history_size);
void printHistory (char history[MAX_HISTORY][BUFFER_LENGTH], int history_size);
int execute(char * fullpath, struct command_t * cmd,
	    char history[MAX_HISTORY][BUFFER_LENGTH], int history_size);
void printCD();
/**---------------------------------------------------------------------- :Main:
//                              __  __      _
//                             |  \/  |__ _(_)_ _
//                             | |\/| / _` | | ' \
//                             |_|  |_\__,_|_|_||_|
//
//----------------------------------------------------------------------------*/
int main( int argc, char *argv[] )
{
    //////////////////////////////
    // Variables
    //////////////////////////////
    char buffer[BUFFER_LENGTH];
    char *dirs[10];
    char fullpath[512];
    char history[MAX_HISTORY][BUFFER_LENGTH];
    int history_size = 0;

    //////////////////////////////
    // Shell Loop
    //////////////////////////////
    printf("welcome to myshell\n");
    int dir_size = parsePath(dirs);
    while(1)
    {
#ifdef PRINTCD
	printCD();
#endif
	struct command_t cmd;
	printf("---| ");
	// Get Command from User
	fgets(buffer, BUFFER_LENGTH, stdin);
	// Store History
	if ( strcmp(buffer, "\n" ))
	    history_size = storeHistory(buffer, history, history_size);

	//////////////////////////////
	// Process Input
	//////////////////////////////
	parseCommand(buffer, &cmd);
#ifdef CMD
	printCommand(&cmd);
#endif
	// Exit the Shell
	if ( !strcmp(cmd.argv[0], "exit" ))
	{
	    int pid;
	    //wait for all processes to finish
	    while ((pid = waitpid(-1, NULL, 0)))
	    {
		if (errno == ECHILD)
		{
		    break;
		}
	    }
	    printf("bye\n");
	    exit(0);
	}

	// Bang Search
	else if ( cmd.argv[0][0] == '!' && strlen(cmd.argv[0]) > 1 )
	{
	    //printf("Searching for %s\n", cmd.argv[0]);
	    int k = history_size -1;
	    int found = 0;
	    while(k >= 0)
	    {
		int max = strlen(cmd.argv[0]) - 1;
		int j = 0;
		int matches = 0;
		for(j=0; j<max; j++)
		{
		    //printf("\t%i   comparing %c to %c\n", k, cmd.argv[0][j+1], history[k][j]);
		    if( cmd.argv[0][j+1] == history[k][j] )
			matches++;
		    else
			break;
		}
		if ( matches == max )
		{
		    found = 1;
		    strcpy(buffer, history[k]);
		    parseCommand(buffer, &cmd);

		    //Otherwise Execute the Command if Possible
		    if( getPath(dirs, dir_size, cmd.argv[0], fullpath) )
			execute(fullpath, &cmd, history, history_size);
		    break;
		}
		k--;
	    }
	    // Unable to find in History
	    if(!found)
	    {
		printf("ERROR: no command with '");
		int i;
		for(i=1; i<strlen(cmd.argv[0]); i++)
		    printf("%c",cmd.argv[0][i]);
		printf("' prefix in history\n");
	    }
	}
	//Execute the Command
	else if ( getPath(dirs, dir_size, cmd.argv[0], fullpath) && strcmp(buffer, "" ) )
	{
	    execute(fullpath, &cmd, history, history_size);
	}
    }
}
/**----------------------------------------------------------------- :Functions:
//                      ___             _   _
//                     | __|  _ _ _  __| |_(_)___ _ _  ___
//                     | _| || | ' \/ _|  _| / _ \ ' \(_-<
//                     |_| \_,_|_||_\__|\__|_\___/_||_/__/
//
//
//----------------------------------------------------------------------------*/
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Reads the PATH variable for this enviroment, then builds an array,
 *  dirs[], of the directories in PATH
 *  @param[out] dirs[] An array of the directories in PATH.
 *  @return The number of directories stored in dirs.
 */
////////////////////////////////////////////////////////////////////////////////
int parsePath( char *dirs[] )
{
    char *pathEnvVar;
    char *thePath;

    int i;
    for(i =0; i<MAX_ARGS; i++)
	dirs[i] = NULL;
    pathEnvVar = (char *) getenv( PATH_VAR );
    if(pathEnvVar == NULL)
    {
	pathEnvVar = "/bin";
    }
    thePath = (char *) malloc(strlen(pathEnvVar) + 1);
    strcpy(thePath, pathEnvVar);

    printf("($MYPATH is %s)\n", thePath);

    /* Loop to parse thePath.
       Look for a ':' delimiter between each path name. */
    int pos = 0;

    char * pch;
#ifdef TOKEN
    printf ("Splitting \"%s\" into tokens:\n",thePath);
#endif
    pch = strtok (thePath,":");
    while (pch != NULL)
    {
#ifdef TOKEN
	printf ("%s\n",pch);
#endif
	// Parse ~ to $HOME Conversions
	if( pch[0] == '~' )
	{
	    pch++;
	    char fullpath[512];
	    char * HOME = (char *) getenv( "HOME" );
	    strcpy (fullpath, HOME);
	    strcat (fullpath, pch);
	    strcpy(pch, fullpath);
	}

	dirs[pos] = pch;
	pch = strtok (NULL, ":");
	pos++;
    }
    return pos;
}
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Searches for the path of the file in dirs.
 *  @param[in] dirs[]
 *  @param[in] dir_size
 *  @param[in] command
 *  @param[out] path_out
 *  @return 1 if the path was found, 0 otherwise.
 */
////////////////////////////////////////////////////////////////////////////////
int getPath (char *dirs[], int dir_size, char * command, char * path_out)
{
    char f_name[512];
    int i;

    if( !strcmp(command, "history") || !strcmp(command, "cd") )
	return 1;

    // Test Local Directory
    if(command[0]);// == '.')
    {
	//strcat(f_name, command);
	if( access( command, F_OK) != -1 )
	{
	    strcpy(path_out, command);
	    return 1;
	}
    }
    // Test Directories in dirs[]
    for(i=0; i<dir_size; i++)
    {
	//Concate and Copy Strings
	strcpy(f_name, dirs[i]);
	strcat(f_name, "/");
	strcat(f_name, command);

	// Found File
	if( access( f_name, F_OK ) != -1 )
	{
	    //printf("Found %s\n", f_name);
	    strcpy(path_out, f_name);
	    return 1;
	}
    }
    printf("ERROR: command '%s' not found\n", command);
    return 0;
}
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Parses a commandline string into the command structure.
 *  @param[in] cLine The commandline command to process.
 *  @param[out] cmd The command structure to store the result in.
 *  @return 1 on success.
 */
////////////////////////////////////////////////////////////////////////////////
int parseCommand(char *cLine, struct command_t *cmd)
{
    int argc;
    char **clPtr;
    // Initialization
    clPtr = &cLine;  //cLine is the command line
    argc = 0;
    cmd->argc = 0;
    cmd->input = NULL;  cmd->output = NULL;
    cmd->hasInput = 0; cmd->hasOutput = 0; cmd->specialOutput = 0;

    cmd->argv[argc] = (char *) malloc(MAX_ARG_LEN);
    while((cmd->argv[argc] = strsep(clPtr, WHITESPACE)) != NULL)
    {
	// Check for ~ to $HOME Conversions
	if( cmd->argv[argc][0] == '~' )
	{
	    cmd->argv[argc]++;
	    char fullpath[512];
	    char * HOME = (char *) getenv( "HOME" );
	    strcpy (fullpath, HOME);
	    strcat (fullpath, cmd->argv[argc]);
	    strcpy(cmd->argv[argc], fullpath);
	}
	//Check for Redirection.
	if(!strcmp(cmd->argv[argc], "==>"))
	{
	    cmd->hasOutput = 1;
	    cmd->output = (char *) malloc(MAX_ARG_LEN);
	    cmd->output = strsep(clPtr, WHITESPACE);
	    argc--;
	}
	else if(!strcmp(cmd->argv[argc], "<=="))
	{
	    cmd->hasInput = 1;
	    cmd->input = (char *) malloc(MAX_ARG_LEN);
	    cmd->input = strsep(clPtr, WHITESPACE);
	    argc--;
	}
	else if(!strcmp(cmd->argv[argc], "-->"))
	{
	    cmd->specialOutput = 1;
	    cmd->output = (char *) malloc(MAX_ARG_LEN);
	    cmd->output = strsep(clPtr, WHITESPACE);
	    argc--;
	}
	cmd->argv[++argc] = (char *) malloc(MAX_ARG_LEN);
    }
    //Set the command name and argc
    cmd->argc += argc-1;
    cmd->name = (char *) malloc(sizeof(cmd->argv[0]));
    strcpy(cmd->name, cmd->argv[0]);
    cmd->argv[cmd->argc] = NULL;

    if(!strcmp(cmd->argv[cmd->argc - 1], "&"))
    {
	cmd->background = 1;
	cmd->argv[cmd->argc - 1] = NULL;
	cmd->argc = cmd->argc - 1;
    }
    else
	cmd->background = 0;
    return 1;
}
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Prints all members of the command line structure
 *  @param[in] cmd The commandline structure to print.
 *  @return void
 */
////////////////////////////////////////////////////////////////////////////////
void printCommand(struct command_t *cmd)
{
    printf("------Printing Command------------------\n");
    printf("cmd Name = %s\nnum Args = %i\n", cmd->name, cmd->argc);
    int i;
    for(i=0; i<cmd->argc; i++)
	printf("\t%i. %s\n", i, cmd->argv[i]);
    printf("Output: %s\n", cmd->output);
    printf("Input: %s\n", cmd->input);
    printf("isBackground: %i\n", cmd->background);
    printf("----------------------------------------\n");
}
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Stores the command in history.
 *  @param[in] cLine The full command to store in histoyr.
 *  @param[in] history To store the history commands in.
 *  @param[in] history_size The current size of the history.
 *  @return The new size of the history.
 */
////////////////////////////////////////////////////////////////////////////////
int storeHistory (char * cLine, char history[MAX_HISTORY][BUFFER_LENGTH],
		  int history_size)
{
    if( history_size < MAX_HISTORY )
	strcpy(history[history_size++], cLine);
    else
    {
	int i;
	for(i=1; i < MAX_HISTORY; i++)
	    strcpy(history[i-1], history[i]);
	strcpy(history[MAX_HISTORY-1], cLine);
    }
    return history_size;
}
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Prints stored history.
 *  @param[in] history The history to print.
 *  @param[in] history_size The num of commands to print from history
 *  @return void
 */
////////////////////////////////////////////////////////////////////////////////
void printHistory (char history[MAX_HISTORY][BUFFER_LENGTH], int history_size)
{
    int i;
    for(i=0; i<history_size; i++)
	printf("%03i %s", i, history[i]);
}
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Executes a file with arguments.
 *  @param[in] fullpath the fullpath including filename of the file to execute.
 *  @param[in] cmd The command arguments.
 *  @return EXIT_FAILURE if unsuccessful.
 */
////////////////////////////////////////////////////////////////////////////////
int execute(char * fullpath, struct command_t * cmd,
	    char history[MAX_HISTORY][BUFFER_LENGTH], int history_size)
{
    // Print History
    if ( !strcmp(cmd->argv[0], "history") )
    {
	printHistory(history, history_size);
	return EXIT_SUCCESS;
    }
    // CD
    else if ( !strcmp(cmd->argv[0], "cd" ) )
    {
	chdir(cmd->argv[1]);
	return EXIT_SUCCESS;
    }
    // General Execute Case
    else
    {
	pid_t pid = fork();
	// Error Handling Condition
	if( pid < 0 )
	{
	    printf("Unable to fork()\n");
	}
	// In the Child Process
	else if ( pid == 0 )
	{
#ifdef VERBOSE
	    printf( "CHILD: happy birthday to me!\n" );
#endif
	    // File Redirection
	    int bak, bak_in, new_in, fd;
	    // ==> Output
	    if(cmd->hasOutput)
	    {
		fflush(stdout);
		bak = dup(1);
		fd = open(cmd->output, O_WRONLY | O_CREAT | O_TRUNC, 0600);
		if (fd < 0)
		{
		    fprintf(stderr, "open error: %d [%s]\n", errno, strerror(errno));
		    exit(1);
		}
		dup2(fd, 1);
		close(fd);
	    }
	    // --> Output
	    if(cmd->specialOutput)
	    {
		fflush(stdout);
		bak = dup(1);
		fd = open(cmd->output, O_WRONLY | O_APPEND, 0600);
		if (fd < 0)
		{
		    fprintf(stderr, "open error: %d [%s]\n", errno, strerror(errno));
		    exit(1);
		}
		dup2(fd, 1);
		close(fd);
	    }
	    // <== Input
	    if(cmd->hasInput)
	    {
		fflush(stdin);
		bak_in = dup(0);
		new_in = open(cmd->input, O_RDONLY);
		dup2(new_in, 0);
		close(new_in);
	    }

	    // Execute the Command
	    execv(fullpath, cmd->argv);

	    // Close File Redirection
	    // @note - not sure this code is ever run
	    if(cmd->hasOutput)
	    {
		fflush(stdout);
		dup2(bak, 1);
		close(bak);
	    }
	    // --------------------
	    if(cmd->hasInput)
	    {
		fflush(stdin);
		dup2(bak_in, 0);
		close(bak_in);
	    }

	    /* if the execv() sys call works, code should never get here */
	    perror( "execv() failed" );
	    return EXIT_FAILURE;
	}
	// In the Parent Process
	else
	{
#ifdef VERBOSE
	    printf( "PARENT: my child's pid is %d\n", pid );
#endif

	    /* wait for the child to terminate */
	    int status;
	    if( cmd->background )
	    {
		waitpid( -1, &status, WNOHANG );   /* NOBLOCKING */
		printf( "[process running in background with pid %i]\n", pid );
	    }
	    else
		waitpid( -1, &status, 0 );         /* BLOCKING */

#ifdef VERBOSE
	    printf( "PARENT: child %d terminated...", child_pid );
	    if ( WIFSIGNALED( status ) )  /* core dump or kill or kill -9 */
	    {

		printf( "abnormally\n" );
	    }
	    else if ( WIFEXITED( status ) )   /* child called return or exit() */
	    {
		int rc = WEXITSTATUS( status );
		printf( "successfully with exit status %d\n", rc );
	    }
#endif
	}
    }
    return EXIT_FAILURE;

}
////////////////////////////////////////////////////////////////////////////////
/**
 *  @brief Prints the current directory.
 *  @return void
 */
////////////////////////////////////////////////////////////////////////////////
void printCD()
{
    char cwd[1024];
    if (getcwd(cwd, sizeof(cwd)) != NULL)
	printf("\nCurrent working dir: %s/\n", cwd);
    else
	perror("getcwd() error");
}
A Simple ls Program
2011 IEEE Robot Course Runs – Videos

Categorized Under

CLanguagesProgrammingProgramsSchoolwork

About Nick Guthrie

» has written 38 posts

History