1

Good afternoon everybody, I am working with tokenizing input in C for a dummy shell program and I am having problems with an infinite loop. In my example code, workwithtokenstuff() is executing infinitely. I have formed similar loops before, I am not sure why the scanf at the bottom is being skipped. Example input is "cd /dummydir". I have another strtok() call to split the dir into another char array.

char inStr[255];
char *token;

scanf("%[^\n\r]", inStr);
token = strtok(inStr, " ");
while (strcmp(token, "exitcom") != 0) {
    workwithtokenstuff();

    scanf("%[^\n\r]", inStr);
    token = strtok(inStr, " ");
}
user2337438
  • 29
  • 1
  • 7
  • What is `workwithtokenstuff()` doing? It may be important to know. – anastaciu Apr 14 '21 at 22:03
  • 1
    `scanf(" %[^\n\r]", inStr);` (note the `' '` before `'['`) Why? `"%[..]"` does NOT discard leading whitespace. You should validate EVERY input, e.g. `if (scanf(" %[^\n\r]", inStr) != 1) { /* handle EOF */ }` – David C. Rankin Apr 14 '21 at 22:09

2 Answers2

2

The second scanf is not really skipped, it's catching the newline character left on the input buffer by the previous scanf, a space before the specifier is a usual fix, it consumes whitespace characters present in the buffer.

char inStr[255];
char *token;

scanf("%254[^\n\r]", inStr);  // note the width limit, avoids buffer overflow

token = strtok(inStr, " ");
while (strcmp(token, "exitcom") != 0)
{
    workwithtokenstuff(); // assuming this does not consume any more tokens
    scanf(" %254[^\n\r]", inStr);
    //     ^ space here
    token = strtok(inStr, " ");
}

Footnotes:

  • For a more robust code, checking the return value of scanf is advised.

  • Maybe you are aware of this, but nonetheless I'll mention that strtok, among other things, changes the original string. If you need more details check How does the strtok function in C work?.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
  • I do consume additional tokens in `worksithtokensandstuff()`. I was not aware that the newline character would be caught by the second `scanf`. I ended up implenting `fgets()` instead of `scanf` as per a peer suggestion, but I experimented with your answer and it also achieves what I am after. Thank you very much for your time! – user2337438 Apr 15 '21 at 18:15
  • 1
    @user2337438 glad to help, I would agree that `fgets` is better, it consumes the `\n` provided that the input is smaller than the destination buffer, the only caveat is that it will be present in the destination buffer, but it can be easily removed if need be. – anastaciu Apr 15 '21 at 18:59
2

Notwithstanding what you do with the data you read, if you do not consume any data from the standard input between the two scanf calls presented then yes, it is to be expected that the second will not consume or convert any additional input. That is because after the first ...

scanf("%[^\n\r]", inStr);

..., if there are any more characters available from the standard input then the next is either a newline or a carriage return (supposing, of course, that the behavior does not end up being undefined on account of overrunning the bounds of inStr). Unlike most scanf field directives, %[ does not skip leading whitespace, so if at that point you perform ...

scanf("%[^\n\r]", inStr);

... again then the first character encountered, if any, is that same carriage return or line feed, which is (again) excluded from the scan set. The scanf call therefore terminates without consuming or converting any characters. It will return 0 or EOF depending on whether in fact there are any characters available to read.

Laying aside the grave risk of a buffer overflow, you need to consume at least one character between the two scanf calls to give the second and subsequent ones a chance to read anything, or else to insert a leading space character into your scanf formats to get the skip over leading whitespace that %[ does not perform automatically. Moreover, you need to check the return value of every scanf call to determine whether it successfully converted any data, and whether there is any point in trying to consume more via subsequent calls.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157