How to make that scanf is optionally ignoring one of its conversion specifiers?

Viewed 302
char value1[10];
int value2;
int value3 = 0;
if (!scanf("%s %d %d", &value1[0], &value2, &value3)) {
    scanf("%s %d", &value1[0], &value2);
}
;

I am trying to use scanf to insert 3 values, but if received 2 it will take in 2 instead. I'm not able to find any answer how to use scanf. I've been trying to use fgets but value in stdin remains.

4 Answers
char value1[10]; int value2; int value3 = 0;
if (!scanf("%s %d %d", &value1[0], &value2, &value3)) { scanf("%s %d", &value1[0], &value2); } ;

One tiny thing: Don't use &value1[0], use value1. An array will decay into a pointer to its first element and, notionally, it's value1 that you want to store the string in.

Second, you need call scanf just once and look at its return value. That will tell you how many values were read in. In this case, you don't really care, so you could use:

if (scanf("%s %d %d", value1, &value2, &value3) < 2)
    { /* error handling */ }
else // we read in 2 or 3 entries, life is good
    { /* success handling */ }

You cannot scanf() more than once. When the second scanf() executes the data is no longer in the input buffer.

char value1[10];
int value2, value3;
int n = scanf("%9s%d%d", value1, &value2, &value3);
// scanf() returns the number of assignments it made, or EOF in case of failure
switch (n) {
    default: fprintf(stderr, "scanf failure\n"); exit(EXIT_FAILURE);
    case 0: strcpy(value1, "default"); /* fall-through */
    case 1: value2 = 42; /* fall-through */
    case 2: value3 = -1; /* fall-through */
    case 3: break;
}
scanf("%s %d %d", &value1[0], &value2, &value3)

The problem is scanf() can´t ignore the third conversion specifier. It still keeps trying to catch the decimal input for the third argument, value3.

To catching the whole input as string first and then later split the content of this string into each own object may be a better alternative.

fgets() is a litte more safer than scanf() when taking user input so I´ll use fgets().

char* fgets ( char * str, int num, FILE * stream );

With fgets() you need to define the amount of characters to read (num), which is a great feature for maintaining security, but in this case we don´t know what a user potentially will input as integer for the decimal input requests. If we specify to less characters for the digits, the rest digits will be left in stdin.

A workaround would be to specify the total amount of digits possible for representing integers of type int in decimal notation in the call to fgets().

These would be 10 digits for the total number of "2,147,483,647" on 64-bit architectures or 5 digits for the total number of "32,767" on 32-bit architectures. I go with the 64-bit case for now.

So 10 (value1) + 10 (value2) + 10 (value3) + 2space characters between value1 and value2 and between value2 and value3 + the terminating \0 for a string = 33 characters. Note that fgets() also reads the entered newline \n (but we later discard that from the string easily) so we need even one character more; it is in total 34 characters:

char buffer[34];
fgets(buffer,sizeof(buffer),stdin);

After that we need to proof how many content we have in the string stored in buffer. We can proof it implicitly by counting the space characters:

unsigned int mark;

for(unsigned int i = 0; i < (sizeof(buffer)/sizeof(buffer[0]) - 1)); i++)
{
   if(buffer[i] == ' ')
     mark++;
}

Thereafter we need to transfer/convert the individual content pieces inside the string in buffer to the appropriate own objects by using sscanf(). If we now have 2 space characters in the string, we can use 3 conversion specifiers in a sscanf command, else we have appropriate sscanf()s:

if(mark == 2)
{
    sscanf(buffer,"%s %d %d", value1, &value2, &value3);
}
   else if(mark == 1)
   {
      sscanf(buffer,"%s %d", value1, &value2);
   }
      else if(mark == 0)
      {
         sscanf(buffer,"%s", value1);
      }
         else
         {
            printf("The input entered is not valid!\n");
            printf("Please try again!\n");
         }

The whole code is then:

#include <stdio.h>
#include <string.h>

int main()
{
    char value1[10];
    int value2;
    int value3;
    unsigned int mark;
    char buffer[34];

    for(;;)
    {
       fgets(buffer,sizeof(buffer),stdin);
       buffer[strcspn(buffer, "\n")] = 0;

       mark = 0;

       for(unsigned int i = 0; i < ((sizeof(buffer)/sizeof(buffer[0]) - 1)); i++)
       {
          if(buffer[i] == ' ')
          mark++;
       }

       if(mark == 2)
       {
          sscanf(buffer,"%s %d %d", value1, &value2, &value3);
          break;
       }
          else if(mark == 1)
          {
             sscanf(buffer,"%s %d", value1, &value2);
             break;
          }
             else if(mark == 0)
             {
                sscanf(buffer,"%s", value1);
                break;
             }
                else
                {
                   printf("The input entered is not valid!\n");
                   printf("Please try again!\n");
                }
    }

    printf("value1 = %s\n", value1);

    if(mark == 1 || mark == 2)
    printf("value2 = %d\n", value2);

    if(mark == 2)
    printf("value3 = %d\n", value3);

    return 0;
}

For flexibility in parsing input, prefer fgets() to fetch lines of input, and sscanf() to parse that input. This allows repeated scans on the same input, when necessary.

To use fgets(), an input buffer is needed. The fgets() function takes an argument describing the size of that input buffer, and reads at most one less character from the input stream. In the event that the user input is larger than this limit, extra input remains in the input stream. This may cause problems for later IO operations, so robust code should handle these extra characters (by reading and either using or discarding them). Simpler code that does not need robustness can simply choose a generously sized input buffer that is unlikely to be overburdened by trusted users. The code below chooses a buffer size of BUFSIZE to be 1024, which should be plenty of extra room, even if a user inadvertently types too many characters at the end of input. Note also that, when there is space for it, the \n character marking the end of a line is read from the input stream and stored in the input buffer. This newline often needs to be removed from the buffer, but in cases involving sscanf() often no further action is needed since the newline should not interfere with scanning.

The sscanf() function (in fact, all fscanf() family functions) respond to the %n directive, which writes the number of characters read from the input up to the occurrence of this directive into a provided integer variable. For sscanf(), this effectively means that when %n is encountered in the format string, the index of the character following the last character read is stored in this provided integer variable.

The code below makes use of this %n facility by first doing num_assigned = sscanf(buffer, "%9s %d%n", value1, &value2, &scan_loc). Here the first two values, which are always expected, are scanned, and the number of characters read to this point is stored in scan_loc. The num_assigned variable holds the number of assignments made by sscanf(), which should be 2. If this is not true, then the program exits with an error message; otherwise the code continues by attempting to parse the rest of the input string. Here, buffer points to the first character of the buffer[] array, and buffer + scan_loc points to the character following the last character read by sscanf(). That is, the second call to sscanf() picks up where the first call left off. If this second call successfully makes an assignment to value3, then num_assigned is incremented.

Note that sscanf() can return 0 or -1 when no assignment is made, depending on circumstances. For this reason, code like: num_assigned += sscanf(buffer + scan_loc, "%d", &value3) is no good since failure to make an assignment could result in decrementing num_assigned. Note also that fgets() is not susceptible to buffer overflow problems when the correct size of the input buffer is provided. If buffer is an array, then sizeof buffer will always provide the correct size (if buffer were a pointer instead, size would have to be obtained another way). But, sscanf() is still susceptible to buffer overflow when using the %s directive. Always specify a maximum field width when using %s (or the %[] scanset directive, for that matter) to avoid buffer overflow when input is larger than expected. A \0 terminator is always written by sscanf() with the %s directive, so to make room use %9s for an array like char value1[10] with a capacity for 10 characters.

#include <stdio.h>
#include <stdlib.h>        // for exit() and EXIT_FAILURE

#define BUFSIZE  1024

int main(void) {
    char value1[10];
    int value2 = 0;
    int value3 = 0;

    char buffer[BUFSIZE];  // generous user input buffer
    int scan_loc = 0;      // scan location in buffer
    int num_assigned = 0;  // number of values assigned by sscanf()

    // fetch user input: parse only if fgets is successful
    if (fgets(buffer, sizeof buffer, stdin) != NULL) {

        // attempt to make first two assignments
        num_assigned = sscanf(buffer, "%9s %d%n", value1, &value2, &scan_loc);
        if (num_assigned == 2) {

            // success: attempt to make third assignment
            int final_assignment = sscanf(buffer + scan_loc, "%d", &value3);
            if (final_assignment == 1) {
                ++num_assigned;
            }
        } else {
            fprintf(stderr, "Input error: %d values read\n", num_assigned);
            exit(EXIT_FAILURE);
        }
        printf("value1: %s, value2: %d", value1, value2);
        if (num_assigned == 3) {
            printf(", value3: %d\n", value3);
        } else {
            putchar('\n');
        }
    }

    return 0;
}

Sample executions:

$ ./a.out 
test2 42
value1: test2, value2: 42
$ ./a.out 
test3 12 34
value1: test3, value2: 12, value3: 34
Related