0

I am trying to write a program that reads 2 words separated by a single space in a single input line.

Normal input should look like this: Apple Banana. Apple should be written to FirstWord and Banana to SecondWord

Currently I am using scanf and reading strings with it. That is my code:

int main (void) {
  char FirstWord[10];
  char SecondWord[10];

  while (true) {
    printf("Enter two words: ");
    scanf("%s", FirstWord);  

    if(!strcmp(FirstWord, "quit")) {
      printf("Exit.");
      return 0;
    }
    scanf("%s", SecondWord);
    printf("First word is %s \nSecond word is %s\n", FirstWord, SecondWord);
  }
}

Program is working, generally speaking, but there is one problem of detecting invalid input from the user.

How can I detect if user enters only one word or empty line? Right now the second scanf waits for the second string in case if only 1 word entered.

Is it possible to deal with that problem with scanf or should I actually get 1 string with fgets and then divide it into two strings?

tadman
  • 208,517
  • 23
  • 234
  • 262
David
  • 45
  • 4
  • 1
    Use `fgets()` to read a line, then parse the line with `sscanf()` or `strtok()` – Barmar Apr 21 '21 at 23:46
  • As you have found, `scanf()` will block waiting on input and it ignores **whitespace**, and a `'\n'` is whitespace, so you can hit Enter as many times as you like and it is simply ignored as input for `scanf()` (unless you are using the `%c` or `%[..]` conversion specifiers). `fgets()` reads and includes the `'\n'` in the buffer it fills completely consuming the line (with a sufficiently sized buffer of course..) – David C. Rankin Apr 21 '21 at 23:59

1 Answers1

1

When you want the user to enter two-words, you want to also be able to handle the case where the user enters one word (maybe thinking they will enter the next word on the next line, etc...) The problem with using scanf() with the "%s" conversion specifier is it ignores leading whitespace and the '\n' character is whitespace. So if only one word is entered, you do not have to opportunity to prompt for the second word because scanf() will block waiting on the second input.

Any time you are taking user input, you are encouraged to use fgets() to fill a buffer (character array) and then parse (separate) the needed values from the buffer. Why? fgets() will consume an entire line of input (given a sufficiently sized buffer). This provides certainty that nothing will remain in stdin unread --- just waiting to bite you on your next input.

You can then use sscanf() to parse values from the buffer just as you would use scanf() to parse values from input. However, you have decoupled the input and conversion, so if a sscanf() conversion fails it will NOT effect your next attempt to read from stdin.

Using fgets() also provides a convenient way of ending input. Since fgets() reads the '\n' character generated by the user pressing Enter you need only check if the first character in the buffer filled is the '\n' character to know the user has simply pressed Enter on a blank line. This can be used as a very convenient way to determine the user is done without requiring additional input such as strcmp(FirstWord, "quit"), etc...

If I understand you want to be able to seamlessly handle either one or two inputs from the user and prompt in the case the user only provides one, you can do:

#include <stdio.h>

#define WORDSZ 32               /* if you need a constant, #define one (or more) */
#define MAXC 1024

int main (void) {
  
  char FirstWord[WORDSZ];                           /* declare arrays */
  char SecondWord[WORDSZ];
  char buf[MAXC];                                   /* buffer to hold entire line */
  
  puts ("press [Enter] on empty line when done.");
  
  while (1) {   /* loop continually until both filled or user cancels */
    fputs ("\nEnter two words: ", stdout);          /* prompt both */
    if (fgets (buf, MAXC, stdin) == NULL) {         /* read into buf, validate */
      puts ("(user canceled input)");
      return 0;
    }
    else if (*buf == '\n')                          /* if [Enter] on empty line */
      break;
    
    /* validate conversion, check if only one word read */
    if (sscanf (buf, "%31s %31s", FirstWord, SecondWord) == 1) {
        while (1) { /* if so, loop until second word read */
            fputs ("Enter second word: ", stdout);  /* prompt 2nd word */
            if (!fgets (buf, MAXC, stdin)) {        /* read into buf, validate */
              puts ("(user canceled input)");
              return 0;
            }
            
            if (sscanf (buf, "%31s", SecondWord) == 1)  /* validate word entered */
              break;
        }
    }
    
    printf ("\nFirst word is %s \nSecond word is %s\n", FirstWord, SecondWord);
  }
}

(note: you must always provide the field-width modifier of one less than your array size for all string conversions to protect your array bounds when using the scanf() family of functions. Otherwise the use of scanf() is no safer than using gets(), See Why gets() is so dangerous it should never be used!)

Since the user canceling input by generating a manual EOF is perfectly valid, you need to check for a manual EOF after each read with fgets() by checking if the return is NULL. A manual EOF is generated by the user pressing Ctrl + d (Ctrl + z on windows).

Example Use/Output

$ ./bin/firstsecondword
press [Enter] on empty line when done.

Enter two words: hello
Enter second word: world

First word is hello
Second word is world

Enter two words: hello world

First word is hello
Second word is world

Enter two words:

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85