// A small elementary unix shell...
// Author: Saket Soni
#include
#include
#include
#include
#include
#include
#include
char ** mainArgv = 0;
int mainArgc = 0;
int lastCommandExitStatus = -1;
int runInBackGround = 0;
char errMsg [1024] ;
int isLoginShell = 0;
char *currentDirectory = 0;
char *oldDirectory = 0;
char homeDir [1024] ;
char *userName = 0;
char hostName [1024] ;
class ShellVariable
{
char * name;
char * value;
public:
ShellVariable ( const char * strName, const char * strValue )
{
if ( strName == NULL || strValue == NULL )
throw "null argument passed to constructor of ShellVariable function ";
name = (char*) malloc ( strlen(strName)+1 );
if ( name == NULL )
throw "out of memory ";
value = (char *) malloc ( strlen(strValue)+1 );
if ( value == NULL )
{
free ( name );
throw "out of memory ";
}
strcpy(name,strName);
strcpy(value,strValue);
}
~ShellVariable ()
{
if ( name )
free ( name );
if ( value )
free ( value );
}
const char * getName ()
{
return name;
}
const char * getValue ()
{
return value;
}
void setValue ( const char * str )
{
if ( str == NULL )
throw "null argument passed to setValue function of ShellVariable Class ";
if ( value )
free ( value );
value = (char *) malloc ( strlen(str)+1 );
if ( value == NULL ) {
if ( name )
free ( name );
throw "out of memory ";
}
strcpy(value,str);
}
bool isEquals ( const char * str )
{
if ( str == NULL )
throw "null argument passed to isEqual function of ShellVariable Class ";
char * pch = strchr(str,'=');
if ( pch == NULL )
return ( strcmp(name,str) == 0 );
const char * ptr1 = str;
char * ptr2 = name;
while ( ptr1 != str )
if ( *ptr1++ != *ptr2++ )
return false;
return true;
}
};
ShellVariable * shellVariableArray[1024];
int numOfShellVariables = 0;
class Command
{
int numArgs;
char * redirStdout;
char * redirStdin;
char * redirStderr;
char * argument[50];
bool isVariableSetCommand;
public:
Command ( const char * str )
{
if ( str == NULL )
throw "invalid command ";
numArgs = 0;
redirStdout = redirStdin = redirStderr = NULL;
isVariableSetCommand = false;
set ( str );
}
bool isVariableSetCmd()
{
return isVariableSetCommand;
}
const char * getName()
{
return argument[0];
}
int getNumArgs()
{
return numArgs;
}
const char * getArg ( int indx )
{
if ( indx < 0 || indx >= numArgs )
return NULL;
return argument[indx];
}
char ** getArgList()
{
return argument;
}
const char * getRedirStdout()
{
return redirStdout;
}
const char * getRedirStderr()
{
return redirStderr;
}
const char * getRedirStdin()
{
return redirStdin;
}
bool isInternalCommand()
{
return ( ( strcmp ( argument[0], "exit" ) == 0 ) ||
( strcmp ( argument[0], "logout" ) == 0 ) ||
( strcmp ( argument[0], "cd" ) == 0 ) ||
( strcmp ( argument[0], "set" ) == 0 ) ||
( strcmp ( argument[0], "echo" ) == 0 ) );
}
void set ( const char * str )
{
char temp[100];
if ( redirStdout )
free ( redirStdout );
if ( redirStdin )
free ( redirStdin );
if ( redirStderr )
free ( redirStderr );
redirStdout = redirStdin = redirStderr = NULL;
char *tempStr = (char*) malloc ( strlen(str)+1 ); // must be freed before exiting from the
function.
if ( tempStr == NULL )
throw "out of memory ";
strcpy(tempStr,str);
char *k1 = strstr(tempStr," 2>");
if ( k1 )
{
char *k2 = strstr(k1+3," 2>");
if ( k2 ) {
free ( tempStr ); tempStr = NULL;
throw "cannot have more than one stderr redirection in a command ";
}
k2 = k1+3;
while ( *k2 == ' ' || *k2 == '\t' )
k2++;
char *k3 = temp;
while ( *k2 != ' ' && *k2 != '\t' && *k2 != '>' && *k2 != '<' && *k2 != 0 )
*k3++ = *k2++;
*k3 = 0;
if ( temp[0] == 0 ) {
free ( tempStr ); tempStr = NULL;
throw "no filename specified for redirecting stderr ";
}
k3 = (char*) malloc ( strlen(temp)+1 );
if ( k3 == NULL ) {
free ( tempStr ); tempStr = NULL;
throw "out of memory ";
}
strcpy(k3,temp);
redirStderr = k3;
while ( *k2 != 0 )
*k1++ = *k2++;
*k1 = 0;
}
k1 = strchr(tempStr,'>');
if ( k1 )
{
char *k2 = strchr(k1+1,'>');
if ( k2 ) {
free ( tempStr ); tempStr = NULL;
throw "cannot have more than one stdout redirection in a command ";
}
k2 = k1+1;
while ( *k2 == ' ' || *k2 == 't' )
*k2++;
char *k3 = temp;
while ( *k2 != ' ' && *k2 != '\t' && *k2 != '<' && *k2 != 0 )
*k3++ = *k2++;
*k3 = 0;
if ( temp[0] == 0 ) {
free ( tempStr ); tempStr = NULL;
throw "no filename specified for redirecting stdout ";
}
k3 = (char*) malloc ( strlen(temp)+1 );
if ( k3 == NULL ) {
free ( tempStr ); tempStr = NULL;
throw "out of memory ";
}
strcpy(k3,temp);
redirStdout = k3;
while ( *k2 != 0 )
*k1++ = *k2++;
*k1 = 0;
}
k1 = strchr(tempStr,'<');
if ( k1 )
{
char *k2 = strchr(k1+1,'<');
if ( k2 ) {
free ( tempStr ); tempStr = NULL;
throw "cannot have more than one stdin redirection in a command ";
}
k2 = k1+1;
while ( *k2 == ' ' || *k2 == 't' )
*k2++;
char *k3 = temp;
while ( *k2 != ' ' && *k2 != '\t' && *k2 != 0 )
*k3++ = *k2++;
*k3 = 0;
if ( temp[0] == 0 ) {
free ( tempStr ); tempStr = NULL;
throw "no filename specified for redirecting stdin ";
}
k3 = (char*) malloc ( strlen(temp)+1 );
if ( k3 == NULL ) {
free ( tempStr ); tempStr = NULL;
throw "out of memory ";
}
strcpy(k3,temp);
redirStdin = k3;
while ( *k2 != 0 )
*k1++ = *k2++;
*k1 = 0;
}
k1 = strtok ( tempStr, " \t" );
if ( k1 == NULL ) {
free ( tempStr ); tempStr = NULL;
throw "invalid command ";
}
argument[0] = (char*) malloc(strlen(k1)+1);
if ( argument[0] == NULL ) {
free ( tempStr ); tempStr = NULL;
throw "out of memory ";
}
strcpy(argument[0],k1);
numArgs = 1;
while ( k1 = strtok(0," \t") )
{
argument[numArgs] = (char*) malloc(strlen(k1)+1);
if ( argument[numArgs] == NULL ) {
free ( tempStr ); tempStr = NULL;
for ( int i = numArgs-1 ; i >= 0 ; i-- )
free ( argument[i] );
numArgs = 0;
throw "out of memory ";
}
strcpy(argument[numArgs],k1);
numArgs++;
}
argument[numArgs] = NULL;
// checking if the command is for setting a shell variable ...
isVariableSetCommand = false;
char * sptr1 = strchr ( argument[0], '=' );
if ( sptr1 != NULL && sptr1 != argument[0] && ( *(sptr1-1) != ' ' ) )
{
strcpy(tempStr,str); // make a fresh copy of str in tempStr !!
sptr1 = strchr ( tempStr, '=' );
if ( *(sptr1+1) != ' ' && *(sptr1+1) != 0 )
{
for ( int i = 0 ; i < numArgs ; i++ )
free ( argument[i] ); // free all previous arguments ...
numArgs = 0;
k1 = strtok ( tempStr, " \t=" ); // find name ...
argument[0] = (char*) malloc(strlen(k1)+1);
if ( argument[0] == NULL ) {
free ( tempStr ); tempStr = NULL;
throw "out of memory ";
}
strcpy(argument[0],k1);
k1 = strtok ( 0, "\n" );
argument[1] = (char*) malloc(strlen(k1)+1);
if ( argument[1] == NULL ) {
free ( tempStr ); tempStr = NULL;
free ( argument[0] ); argument[0] = NULL;
throw "out of memory ";
}
strcpy(argument[1],k1);
numArgs = 2;
isVariableSetCommand = true;
}
else {
sptr1++;
bool nullValue = true;
while ( *sptr1 != 0 )
{
if ( *sptr1 != ' ' && *sptr1 != '\t' ) {
nullValue = false;
break;
}
sptr1++;
}
if ( nullValue == true )
{
for ( int i = 0 ; i < numArgs ; i++ )
free ( argument[i] ); // free all previous arguments ...
numArgs = 0;
k1 = strtok ( tempStr, " \t=" ); // find name ...
argument[0] = (char*) malloc(strlen(k1)+1);
if ( argument[0] == NULL ) {
free ( tempStr ); tempStr = NULL;
throw "out of memory ";
}
strcpy(argument[0],k1);
numArgs = 1;
isVariableSetCommand = true;
}
}
}
// adding color to the ls command ...
if ( strcmp ( argument[0], "ls" ) == 0 ) // if ls, then add --color
{
bool hasColor = false;
for ( int i = 1 ; i < numArgs ; i++ )
if ( strncmp ( argument[i], "--color", sizeof("--color")-1 ) == 0 ) {
hasColor = true;
break;
}
if ( ! hasColor )
{
k1 = NULL;
k1 = (char*) malloc ( sizeof("--color") );
if ( k1 == NULL ) {
free ( tempStr ); tempStr = NULL;
throw "out of memory ";
}
strcpy ( k1, "--color" );
argument[numArgs++] = k1;
argument[numArgs] = 0;
}
}
free ( tempStr );
}
void display()
{
printf ( "Command : ", numArgs );
for ( int i = 0 ; i < numArgs ; i++ )
printf ( "%s ",argument[i] );
printf ( "\n numArgs : %d\n", numArgs );
for ( int i = 0 ; i < numArgs ; i++ )
printf ( " %d.) %s\n", i, argument[i] );
printf ( " Stdout redirection : %s\n", redirStdout);
printf ( " Stderr redirection : %s\n", redirStderr);
printf ( " Stdin redirection : %s\n", redirStdin);
}
bool equals ( const char * str )
{
if ( numArgs == 0 )
return false;
return ( ( strcmp ( argument[0], str ) == 0 ) ? true : false );
}
~Command()
{
if ( redirStdout )
free ( redirStdout );
if ( redirStdin )
free ( redirStdin );
if ( redirStderr )
free ( redirStderr );
for ( int i = 0 ; i < numArgs ; i++ )
if ( argument[i] )
free ( argument[i] );
}
};
void evaluate$ ( char * str )
{
char shellVarName[100];
char mystr[1024];
if ( str == NULL )
return;
char * ptr1 = strchr ( str, '$' );
if ( ptr1 == NULL )
return;
do
{
char ch = *(ptr1+1);
if ( ch == 0 )
return;
if ( ch == '\t' || ch == ' ' )
continue;
*ptr1++ = 0;
strcpy ( mystr, str );
int i = 0;
char *ptr2 = ptr1;
while ( *ptr2 != 0 && *ptr2 != ' ' && *ptr2 != '\t' )
shellVarName[i++] = *ptr2++;
shellVarName[i] = 0;
char *ptr3 = ptr1;
while ( *ptr2 != 0 )
*ptr3++ = *ptr2++;
*ptr3 = 0;
bool doesVariableExists = false;
i = 0;
for ( i = 0 ; i < numOfShellVariables ; i++ )
if ( shellVariableArray[i]->isEquals(shellVarName) ) {
doesVariableExists = true;
break;
}
if ( doesVariableExists == true )
strcat ( mystr, shellVariableArray[i]->getValue() );
strcat ( mystr, ptr1 );
strcpy ( str, mystr );
} while ( ptr1 = strchr ( str, '$' ) );
}
int main ( int argc, char * argv[] )
{
int counter1 = -1;
int inputLen = 0;
char input [1024] ;
Command *command [100];
int numCommands = 0;
int myStdin = -1;
int myStdout = -1;
int myStderr = -1;
// make command line arguments accessible to all functions ...
mainArgv = argv;
mainArgc = argc;
// check whether the shell is a login shell or not ...
if ( strcmp ( argv[0], "-shell" ) == 0 )
isLoginShell = 1;
for ( counter1 = 1 ; counter1 < argc ; counter1++ )
{
if ( strcmp ( argv[counter1],"-l" ) == 0 )
isLoginShell = 1;
else if ( strcmp ( argv[counter1], "--login" ) == 0 )
isLoginShell = 1;
else {
fprintf ( stderr,"shell: invalid argument!\n" );
return 1;
}
}
// adding path entry in the list of shell varibles ...
// get basic information about user and his/her environment ...
currentDirectory = get_current_dir_name();
userName = getlogin();
gethostname ( hostName, sizeof ( hostName ) );
strcpy(homeDir,currentDirectory);
while ( 1 ) // infinite loop for reading commands ...
{
// show prompt to the user ...
fflush ( stdin );
printf ( "[%s@%s %s]\n$ ", userName, hostName, currentDirectory );
fflush ( stdout );
// read command from the user ...
inputLen = read ( 0, input, sizeof(input) );
char * str1 = strchr(input,'\n');
if ( str1 != NULL ) // removing '\n' ...
*str1 = 0;
str1 = strchr(input,'\r');
if ( str1 != NULL ) // removing '\r' ...
*str1 = 0;
// find and evalute $'s ...
evaluate$ ( input );
// check whether to run in background or not ...
str1 = strchr ( input, '&' );
runInBackGround = 0;
if ( str1 )
{
char * str2 = strrchr ( input, '&' );
if ( str1 != str2 ) {
fprintf ( stderr, "%s: systax error near unexpected token \'&\'\n",
mainArgv[0] );
continue;
}
else { // set global variable to indicate run in background ...
runInBackGround = 1;
*str1++ = 0;
}
}
// delete all previous commands ...
for ( int i = 0 ; i < numCommands ; i++ )
delete command[i];
numCommands = 0;
// parse the input to check if it is set variable command ...
Command * temp = NULL ;
try {
temp = new Command (input) ;
}
catch ( const char * estr ) {
fprintf ( stderr, "%s: %s\n", mainArgv[0], estr );
continue;
}
if ( temp->isVariableSetCmd() )
{
bool doesVariableExists = false;
int i = 0;
for ( i = 0 ; i < numOfShellVariables ; i++ )
if ( shellVariableArray[i]->isEquals(temp->getName()) ) {
doesVariableExists = true;
break;
}
if ( doesVariableExists == true ) {
if ( temp->getNumArgs() == 2 ) {
shellVariableArray[i]->setValue(temp->getArg(1));
delete temp;
}
else {
delete shellVariableArray[i];
for ( int k = i+1 ; k < numOfShellVariables ; k++ )
shellVariableArray[k-1] = shellVariableArray[k];
numOfShellVariables--;
delete temp;
}
}
else {
if ( temp->getNumArgs() == 2 ) {
shellVariableArray[numOfShellVariables] = new ShellVariable(
temp->getName(),
temp->getArg(1) );
numOfShellVariables++;
delete temp;
}
}
continue;
}
else
delete temp;
// parse the input to find the various commands ...
str1 = input;
char *str2 = strchr ( input, '|' );
if ( str2 )
{
*str2 = 0;
try {
temp = new Command(str1);
}
catch ( const char * estr ) {
fprintf ( stderr, "%s: %s\n", mainArgv[0], estr );
continue;
}
command[0] = temp;
numCommands++;
try {
str1 = str2+1;
str2 = strchr ( str1, '|' );
while ( str2 ) {
*str2 = 0;
temp = new Command ( str1 );
command[numCommands++] = temp;
str1 = str2+1;
str2 = strchr ( str1, '|' );
}
temp = new Command ( str1 );
command[numCommands++] = temp;
}
catch ( const char * estr ) {
fprintf ( stderr, "%s: %s\n", mainArgv[0], estr );
numCommands = 0;
continue;
}
}
else
{
try {
temp = new Command(input);
}
catch ( const char * estr ) {
fprintf ( stderr, "%s: %s\n", mainArgv[0], estr );
continue;
}
command[0] = temp;
numCommands++;
}
if ( numCommands > 1 ) // if one of the commands is an internal command then cannot use pipe!!
{
bool foundInternalCommand = false;
int i;
for ( i = 0 ; i < numCommands ; i++ )
if ( command[i]->isInternalCommand() ) {
foundInternalCommand = true;
break;
}
if ( foundInternalCommand )
{
sprintf ( errMsg, "%s: cannot use '%s' with pipes ", mainArgv[0],
command[i]->getName() );
perror ( errMsg );
continue;
}
}
if ( numCommands == 1 )
{
// check for internal commands and execute ...
if ( strcmp ( command[0]->getName(), "exit" ) == 0 ) // exit ...
{
if ( isLoginShell )
exit ( 0 );
switch ( command[0]->getNumArgs() )
{
case 1:
_exit ( lastCommandExitStatus );
case 2: {
const char * str3 = command[0]->getArg(1);
int len = strlen ( str3 );
bool isNumber = true;
for ( int i = 0 ; i < len ; i++ )
if ( ! isdigit ( str3[i] ) ) {
isNumber = false;
break;
}
if ( isNumber == true )
_exit ( atoi ( str3 ) );
else
fprintf ( stderr, "%s: exit: %s: numeric argument required",
mainArgv[0], str3 );
}
break;
default:
fprintf ( stderr, "%s: exit: too many arguments", mainArgv[0] );
}
}
else if ( strcmp ( command[0]->getName(), "logout" ) == 0 ) // logout ...
{
if ( ! isLoginShell )
fprintf ( stderr, "%s: logout: not login shell: use \'exit\'\n",
mainArgv[0] );
else
_exit ( 0 );
}
else if ( strcmp ( command[0]->getName(), "cd" ) == 0 ) // cd ...
{
int chdirStatus = -1;
if ( command[0]->getNumArgs() == 1 )
chdirStatus = chdir ( homeDir );
else if ( strcmp ( command[0]->getArg(1), "-" ) == 0 ) {
if ( oldDirectory )
chdirStatus = chdir ( oldDirectory );
else
chdirStatus = 0;
}
else
chdirStatus = chdir ( command[0]->getArg(1) );
if ( chdirStatus == -1 ) {
sprintf ( errMsg, "%s: cd: %s", mainArgv[0], command[0]->getArg(1) );
perror ( errMsg );
}
else {
if ( oldDirectory )
free ( oldDirectory );
oldDirectory = currentDirectory ;
currentDirectory = get_current_dir_name ();
}
}
else if ( strcmp ( command[0]->getName(), "set" ) == 0 ) // set ...
{
for ( int i = 0 ; i < numOfShellVariables ; i++ )
fprintf(stdout,"%s=%s\n",shellVariableArray[i]->getName(),shellVariableArray[i]->getValue());
}
else
// external command ...
{
int childPId = fork();
switch ( childPId )
{
case 0: // child process starts here ...
if ( command[0]->getRedirStdout() ) { // stdout
redirection ...
int fd = creat ( command[0]->getRedirStdout(), 00644 );
close(1);
dup(fd);
close(fd);
}
if ( command[0]->getRedirStdin() ) { // stdin
redirection ...
int fd = open ( command[0]->getRedirStdin(), O_RDONLY );
if ( fd == -1 ) {
sprintf ( errMsg, "%s: %s", mainArgv[0],
command[0]->getName() );
perror ( errMsg );
_exit(1);
}
close(0);
dup(fd);
close(fd);
}
if ( command[0]->getRedirStderr() ) { // stderr
redirection ...
int fd = creat ( command[0]->getRedirStderr(), 00644 );
close(2);
dup(fd);
close(fd);
}
execvp ( command[0]->getName(), command[0]->getArgList() ) ; // start new
program ...
sprintf ( errMsg, "%s: %s", mainArgv[0], command[0]->getName() );
perror ( errMsg );
_exit (1) ; // error child process ends here ...
case -1: // if no child has been forked ...
sprintf ( errMsg, "%s: %s", mainArgv[0], command[0]->getName() );
perror ( errMsg );
break;
default: // if parent process continues here ...
if ( runInBackGround == 0 )
{
int waitStatus = -1;
do
{
waitStatus = wait ( &lastCommandExitStatus );
if ( waitStatus == -1 )
{
sprintf ( errMsg, "%s: %s", mainArgv[0],
command[0]->getName() );
perror ( errMsg );
break;
}
} while ( waitStatus != childPId ); // remove all dead child
processes ...
}
}
}
}
else // pipes exists ( with only external commands ) ...
{
int childPId = fork();
switch ( childPId )
{
case 0: // child process starts here ...
{
int comK = numCommands-2;
int pipefdR[2];
pipe ( pipefdR );
int lastInputFd = pipefdR[0];
int firstOutputFd = -1;
int pipefdL[2];
for ( ; comK > 0 ; comK-- )
{
pipe ( pipefdL );
int grandChild = fork();
if ( grandChild == 0 ) // grandchild process starts here
...
{
if ( command[comK]->getRedirStderr() ) { //
stderr redirection ...
int fd = creat ( command[comK]->getRedirStderr(),
00644 );
close(2);
dup(fd);
close(fd);
}
close(1);
dup(pipefdR[1]); // replacing stdout with
output to pipe on the right!
close(pipefdR[1]);
close(pipefdR[0]);
close(0);
dup(pipefdL[0]); // replacing stdin with
input from pipe on the left!
close(pipefdL[0]);
close(pipefdL[1]);
execvp ( command[comK]->getName(),
command[comK]->getArgList() ) ; // start new
// process ...
sprintf ( errMsg, "%s: %s", mainArgv[comK], command[comK]->getName()
);
perror ( errMsg );
// killpg(0,SIGKILL);
_exit(1) ; // on error grandchild process ends here ...
}
else if ( grandChild == -1 )
{
sprintf ( errMsg, "%s: %s", mainArgv[0],
command[comK]->getName() );
perror ( errMsg );
// killpg(0,SIGKILL);
_exit(1);
}
else { // child,
i.e parent of grandchild ...
close ( pipefdR[1] );
close ( pipefdL[0] );
pipefdR[1] = pipefdL[1];
}
}
firstOutputFd = pipefdR[1];
int grandChild = fork();
if ( grandChild == 0 )
{
if ( command[0]->getRedirStdin() ) { // stdin
redirection ...
int fd = open ( command[0]->getRedirStdin(), O_RDONLY );
if ( fd == -1 ) {
sprintf ( errMsg, "%s: %s", mainArgv[0],
command[0]->getName() );
perror ( errMsg );
_exit(1);
}
close(0);
dup(fd);
close(fd);
}
if ( command[0]->getRedirStderr() ) { // stderr
redirection ...
int fd = creat ( command[0]->getRedirStderr(), 00644 );
close (2);
dup(fd);
close(fd);
}
close(1);
dup(firstOutputFd); // replacing stdout with output to pipe on
the right!
close(firstOutputFd);
execvp ( command[0]->getName(), command[0]->getArgList() ) ; //
start new process ...
sprintf ( errMsg, "%s: %s", mainArgv[0], command[0]->getName() );
perror ( errMsg );
// killpg(0,SIGKILL);
_exit (1) ; // on error grandchild process ends here ...
}
else if ( grandChild == -1 )
{
sprintf ( errMsg, "%s: %s", mainArgv[0], command[0]->getName() );
perror ( errMsg );
// killpg(0,SIGKILL);
_exit(1);
}
// child, i.e
parent of grandchild ...
close(firstOutputFd);
close(0);
dup(lastInputFd);
close(lastInputFd);
comK = numCommands-1;
execvp ( command[comK]->getName(), command[comK]->getArgList() ) ; //
start new process ...
sprintf ( errMsg, "%s: %s", mainArgv[0], command[comK]->getName() );
perror ( errMsg );
// killpg(0,SIGKILL);
_exit (1) ; // on error grandchild process ends here ...
}
case -1: // if no child has been forked ...
sprintf ( errMsg, "%s: %s", mainArgv[0], command[0]->getName() );
perror ( errMsg );
break;
default: // parent process continues here ...
if ( runInBackGround == 0 )
{
int waitStatus = -1;
do
{
waitStatus = wait ( &lastCommandExitStatus );
if ( waitStatus == -1 )
{
sprintf ( errMsg, "%s: %s", mainArgv[0],
command[0]->getName() );
perror ( errMsg );
break;
}
} while ( waitStatus != childPId ); // remove all dead child processes ...
}
}
}
} // end of the infinite loop of the shell !!!
return 0;
}