0

I am currently creating a simple shell for homework and I've run into a problem. Here is a snippet of code with the pieces that pertain to the problem (I may have forgotten some pieces please tell me if you see anything missing):

eatWrd returns the first word from a string, and takes that word out of the string.

wrdCount, as implied, returns the number of words in a string.

if either of these codes are necessary for a response I can post them, just please tell me, I am almost 100% positive they are not the cause of the problem.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 100

int main(void)
{
  char input[MAX];
  char *argm[MAX];
  memset(input, 0, sizeof(input));
  memset(argm, 0, sizeof(argm));

  while(1){
    printf("cmd:\n");
    fgets(input, MAX-1, stdin);

    for(i=0;i < wrdCount(input); i++){
      argm[i] = eatWrd(input);
    }
    argm[i] = NULL;

    if (!strncmp(argm[0],"cd" , 2)){
      chdir(argm[1]);
    }

    if (!strncmp(argm[0],"exit", 4)){
      exit(0);
    }

    memset(input, 0, sizeof(input));
    memset(argm, 0, sizeof(argm));
  }
}

Anyways, this loop works for lots of other commands using execvp, (such as cat, ls, etc.), when I use cd, it works as expected, except when I try to exit the shell, it takes multiple exit calls to actually get out. (as it turns out, the number of exit calls is exactly equal to the number of times I call cd). It only takes one exit call when I don't use cd during a session. I'm not really sure what's going on, any help is appreciated, thanks.

Here is eatWrd:

char* eatWrd(char * cmd)
{
  int i = 0;            // i keeps track of position in cmd
  int count = 0;        // count keeps track of position of second word
  char rest[MAX_LINE];  // rest will hold cmd without the first word

  char * word = (char *) malloc(MAX_LINE);   //word will hold the first word
  sscanf(cmd, "%s", word);                   //scan the first word into word

  // iterate through white spaces, then first word, then the following white spaces
  while(cmd[i] == ' ' || cmd[i] == '\t'){
    i++;
    count++;
  }

  while(cmd[i] != ' ' && cmd[i] != '\t' && cmd[i] != '\n' && cmd[i] != '\0'){
    i++;
    count++;
  }

  while(cmd[i] == ' ' || cmd[i] == '\t'){
    i++;
    count++;
  }

  // copy the rest of cmd into rest
  while(cmd[i] != '\n' && cmd[i] != '\0'){
    rest[i-count] = cmd[i];
    i++;
  }
  rest[i-count] = '\0';

  memset(cmd, 0, MAX_LINE);
  strcpy(cmd, rest);        //move rest into cmd
  return word;              //return word
}

And here is wrdCount:

int wrdCount(char *sent)
{
  char *i = sent;
  int words = 0;

  //keep iterating through the string, 
  //increasing the count if a word and white spaces are passed,
  // until the string is finished.
  while(1){
    while(*i == ' ' || *i == '\t') i++;

    if(*i == '\n' || *i == '\0') break;

    words++;

    while(*i != ' ' && *i != '\t' && *i != '\n' && *i != '\0') i++;
  }
  return words;
}
quigs
  • 23
  • 5
  • Are you getting any extra/missing `cmd:` prompts? And try using a debugger. It will point to your problem right away. – Eugene Sh. Feb 02 '15 at 18:07
  • and what does gdb say is heppening – pm100 Feb 02 '15 at 18:14
  • You're not showing `wrdCount` or `eatWrd`. I'd certainly be suspicious of those at this point. – lurker Feb 02 '15 at 18:16
  • And I have a feeling, that the left out functions are messing with the input pointers. So yes, show them as well – Eugene Sh. Feb 02 '15 at 18:16
  • `eatWrd()` has a very unfortunate signature. It will be tricky (but not impossible) to implement it correctly and safely. I'd drop both `wrdCount()` and `eatWrd()` and use `strtok()` instead. That's what it's for. – John Bollinger Feb 02 '15 at 18:41
  • Note that it is unnecessary to pass `MAX - 1` as the second argument to `fgets()`, as `fgets()` already reads at most one less than that number. Therefore, one would usually pass `MAX` to avoid wasting one byte of buffer. – John Bollinger Feb 02 '15 at 18:44
  • 1
    It's strange to use `strncmp()` to identify built-ins when you can rely upon both inputs to be null-terminated. Consider that all of these will be accepted as equivalent by the above code: `cd`, `cd1`, `cdoops`, `cd...NOT!`. – John Bollinger Feb 02 '15 at 19:02
  • If you're going to bother to zero-out the `input` and `argm` arrays, which shouldn't be necessary, at least be sure to do it before the first loop iteration, too. – John Bollinger Feb 02 '15 at 19:04
  • Also, you have the sense of your `strncmp()` calls reversed. The function returns `0` if the two (sub)strings compared match exactly. – John Bollinger Feb 02 '15 at 19:09
  • sorry ya the strncmp() thing was just a copying mistake, plus the reason I use it specifically would make more sense if I displayed my whole code but its a little long. ill edit in the other functions too. Haven't used gdb yet, but probably a good idea. Yes I am getting extra cmd: prompts after typing exit if I call cd during a session. Also, ill zero- out the initial loop as well. thanks for all the comments. – quigs Feb 02 '15 at 19:26
  • Does the code shown compile? I see `chdir(arg[1]);` instead of the expected `chdir(argm[1]);` -- wrong variable? And you need to handle `cd` with no argument differently from `cd argument`. Also, you should not call `wrdCount` in the loop condition; call it once before the loop. – Jonathan Leffler Feb 02 '15 at 19:48
  • yes, again this is just snippets of my code to illustrate the problem, my actual code compiles and runs just fine, i'll change that right now. – quigs Feb 02 '15 at 19:51

1 Answers1

0

This variation on your code works for me:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#define MAX 100

char *eatWrd(char **line) {
    char *next_c = *line;
    char *word_start = NULL;

    while (isspace(*next_c)) next_c += 1;

    if (*next_c) {
        word_start = next_c;
        do {
            next_c += 1;
        } while (*next_c && ! isspace(*next_c));
        *next_c = '\0';
        *line = next_c + 1;
    }

    return word_start;
}

int main(void)
{
    char input[MAX];
    char *argm[MAX];

    while(1) {
        int word_count = 0;
        char *next_input = input;

        printf("cmd:\n");
        fgets(input, MAX, stdin);

        do {
              argm[word_count] = eatWrd(&next_input);
        } while (argm[word_count++]);
        /* The above always overcounts by one */
        word_count -= 1;

        if (!strcmp(argm[0], "cd")){
            chdir(argm[1]);
        } else if (!strcmp(argm[0], "exit")) {
            exit(0);
        }
    }
}

Note my variation on eatWrd(), which does not have to move any data around, and which does not require pre-parsing the string to determine how many words to expect. I suppose your implementation would be more complex, so as to handle quoting or some such, but it could absolutely follow the same general approach.

Note, too, my correction to the command-matching conditions, using !strcmp() instead of strncmp().

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Ok thanks a lot, this was very insightful, I would upvote your answer but I don't have enough reputation to vote yet – quigs Feb 02 '15 at 19:48
  • @quigs, if I have adequately answered your question then the conventional thing to do would be to accept the answer (by clicking on the check mark next to it). You don't need rep for that. – John Bollinger Feb 02 '15 at 20:03