mloomis mloomis - 2 months ago 13
Bash Question

Create a shell in c that recognizes comments

I am trying to write a shell in c that will recognize # as a comment character. For example, if I type the command "ls # This is a comment", the program should not recognize the characters after the #.

Here is the code I currently have:

/* See Chapter 5 of Advanced UNIX Programming: http://www.basepath.com/aup/
* for further related examples of systems programming. (That home page
* has pointers to download this chapter free.
*
* Copyright (c) Gene Cooperman, 2006; May be freely copied as long as this
* copyright notice remains. There is no warranty.
*/

/* To know which "includes" to ask for, do 'man' on each system call used.
* For example, "man fork" (or "man 2 fork" or man -s 2 fork") requires:
* <sys/types.h> and <unistd.h>
*/
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>

#define MAXLINE 200 /* This is how we declare constants in C */
#define MAXARGS 20

/* In C, "static" means not visible outside of file. This is different
* from the usage of "static" in Java.
* Note that end_ptr is an output parameter.
*/
static char * getword(char * begin, char **end_ptr) {
char * end = begin;

while ( *begin == ' ' )
begin++; /* Get rid of leading spaces. */
end = begin;
while ( *end != '\0' && *end != '\n' && *end != ' ' )
end++; /* Keep going. */
if ( end == begin )
return NULL; /* if no more words, return NULL */
*end = '\0'; /* else put string terminator at end of this word. */
*end_ptr = end;
if (begin[0] == '$') { /* if this is a variable to be expanded */
begin = getenv(begin+1); /* begin+1, to skip past '$' */
if (begin == NULL) {
perror("getenv");
begin = "UNDEFINED";
}
}
return begin; /* This word is now a null-terminated string. return it. */
}

/* In C, "int" is used instead of "bool", and "0" means false, any
* non-zero number (traditionally "1") means true.
*/
/* argc is _count_ of args (*argcp == argc); argv is array of arg _values_*/
static void getargs(char cmd[], int *argcp, char *argv[])
{
char *cmdp = cmd;
char *end;
int i = 0;

/* fgets creates null-terminated string. stdin is pre-defined C constant
* for standard intput. feof(stdin) tests for file:end-of-file.
*/
if (fgets(cmd, MAXLINE, stdin) == NULL && feof(stdin)) {
printf("Couldn't read from standard input. End of file? Exiting ...\n");
exit(1); /* any non-zero value for exit means failure. */
}
while ( (cmdp = getword(cmdp, &end)) != NULL ) { /* end is output param */
/* getword converts word into null-terminated string */

if (strchr(cmdp, '#') != NULL) {

}

argv[i++] = cmdp;
/* "end" brings us only to the '\0' at end of string */
cmdp = end + 1;
}
argv[i] = NULL; /* Create additional null word at end for safety. */
*argcp = i;
}

static void execute(int argc, char *argv[])
{
pid_t childpid; /* child process ID */

childpid = fork();
if (childpid == -1) { /* in parent (returned error) */
perror("fork"); /* perror => print error string of last system call */
printf(" (failed to execute command)\n");
}
if (childpid == 0) { /* child: in child, childpid was set to 0 */
/* Executes command in argv[0]; It searches for that file in
* the directories specified by the environment variable PATH.
*/
if (-1 == execvp(argv[0], argv)) {
perror("execvp");
printf(" (couldn't find command)\n");
}
/* NOT REACHED unless error occurred */
exit(1);
} else /* parent: in parent, childpid was set to pid of child process */
waitpid(childpid, NULL, 0); /* wait until child process finishes */
return;
}

int main(int argc, char *argv[])
{
char cmd[MAXLINE];
char *childargv[MAXARGS];
int childargc;

while (1) {
printf("%% "); /* printf uses %d, %s, %x, etc. See 'man 3 printf' */
fflush(stdout); /* flush from output buffer to terminal itself */
getargs(cmd, &childargc, childargv); /* childargc and childargv are
output args; on input they have garbage, but getargs sets them. */
/* Check first for built-in commands. */
if ( childargc > 0 && strcmp(childargv[0], "exit") == 0 )
exit(0);
else if ( childargc > 0 && strcmp(childargv[0], "logout") == 0 )
exit(0);
else
execute(childargc, childargv);
}
/* NOT REACHED */
}

Answer

You have to put the test outside of the while loop.

static void getargs(char cmd[], int *argcp, char *argv[])
{
    char *cmdp = cmd;
    char *end, *hash;
    int i = 0;

    /* fgets creates null-terminated string. stdin is pre-defined C constant
     *   for standard intput.  feof(stdin) tests for file:end-of-file.
     */
    if (fgets(cmd, MAXLINE, stdin) == NULL && feof(stdin)) {
        printf("Couldn't read from standard input. End of file? Exiting ...\n");
        exit(1);  /* any non-zero value for exit means failure. */
    }
    // check if we have a comment
    hash = strchr(cmd,'#');
    if(hash != NULL){
       // just overwrite it with NULs
       while(*hash != '\0'){
          *hash = '\0';
          hash++;
       }
    }
    while ( (cmdp = getword(cmdp, &end)) != NULL  ) { /* end is output param */
        /* getword converts word into null-terminated string */
        argv[i++] = cmdp;
        /* "end" brings us only to the '\0' at end of string */
    cmdp = end + 1;
    }
    argv[i] = NULL; /* Create additional null word at end for safety. */
    *argcp = i;
}