0

Warning, I'm super new with C language (and coding) so please explain to me like I'm simple :) So, I'm trying to get my case 'A' and case 'B' to give this message "printf("A(or B) should be followed by exactly 2 arguments.\n");" if zero arguments are given(just pressing enter), or only one, or more than 2. Now it acts like this:

From my terminal:

O Pelit_tiedosto.txt

SUCCESS

L

Mario 10.99 131.88

GameName....WithSomeDots 10.99 120.89

Tetris 5.99 59.90

Street_Fighter 6.99 41.94 SUCCESS

B Mario 5 SUCCESS

B Mario

7

SUCCESS

B

(pressing enter multiple times)

mario

6

Game not found: mario

B (pressing enter multiple times)

Mario

h

Number of bought items cannot be less than 1.

Invalid command h

Here is that part of my code:

while (1) {
    scanf(" %c", &command);
    
    switch (command) {
        case 'A': // Adds a new entry to the program database.
            if (scanf("%s %f", name, &price) != 2) {
                printf("A should be followed by exactly 2 arguments.\n");
            } else {
                getchar(); // Consume the newline character
                addGame(&games, &numGames, name, price); // Pass pointer to games
            }
            break;
        case 'B': // Buy game command, which sells a specified number of games
            if (scanf("%s %d", name, &count) != 2 || count <= 0) {
                //getchar(); // Consume the newline character
                printf("Number of bought items cannot be less than 1.\n");
            } else {
                getchar(); // Consume the newline character
                buyGame(games, numGames, name, count);
            }
            break;

        case 'L': //Prints a list of the program database into the standard output
            printDatabase(games, numGames);
            break;

        case 'W': //Writes the program database into a text file
            scanf("%s", filename);
            getchar();
            saveToFile(games, numGames, filename);
            break;

        case 'O': //Loads the database from the specified text file
            scanf("%s", filename);
            getchar();
            loadFromFile(&games, &numGames, filename); // Pass pointer
            break;

        case 'Q':
                    printf("SUCCESS\n");
                    releaseMemory(games, numGames);
                    return 0;
                case '\n':
                    break;
                default:
                    printf("Invalid command %c\n", command);
                    // Clear the input buffer
                    while (getchar() != '\n');
            }
        }

Hopefully my explanation was understandable, English is not my first language :) Thank you for your help in advance!

  • 2
    `scanf()` will loiter waiting for all the items to be entered... Look into `fgets()` to get whatever the user types, then try `sscanf()` on that buffer to extract the bits. `sscanf()` won't _loiter_ waiting for the user to type more... – Fe2O3 Aug 23 '23 at 08:24
  • Just to make sure I understand the question correct.... For A and B you want the input to be on exactly one line and have exactly two arguments (where the second argument must be a an integer number. Is that it? – Support Ukraine Aug 23 '23 at 08:48
  • Yes one line, A and that price is a float :) And with B it's B and the count is integer. Now after typing A or B, if i only press enter or give only one argument, it should give the error message but it doesn't, and after pressing only enter it give nothing until i type something. I hope this makes sense :) – Ronja Oksanen Aug 23 '23 at 12:16
  • And thank you Fe203 for the tip, I will look into sscanf() :) – Ronja Oksanen Aug 23 '23 at 12:17
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community Aug 29 '23 at 01:22

1 Answers1

0

The scanf specifier %s consumes and discards any leading whitespace, including newlines, until it encounters a non-whitespace character (at which point it reads as many non-whitespace characters as specified1).

This is easy to demonstrate with the following program:

#include <stdio.h>

int main(void)
{
    char buf[64];

    if (1 == scanf("%63s", buf))
        printf("<%s>\n", buf);
}

In this example, there are two newlines and seven spaces before the first non-whitespace character

$ ./a.out


     hello
<hello>

which are all consumed and discarded.

This is not something that is easily addressable with scanf, as skipping whitespace (and waiting on unfulfilled conversions) is built into the function.


The usual solution to this is to always read entire lines of input when creating an interactive program, even for simple input (e.g., a single character), as it helps maintain the status2 of the input buffer.

fgets can be used to read lines of input, and sscanf (and/or strspn, strcspn, strchr, strtok, strtol, strncmp, etc.) can be used to parse the resulting string. This has the advantage of allowing you to parse the same input repeatedly, and avoids problems with scanf getting "stuck" on bad user input.

Here is an example that reads lines of input, parses the command name (first whitespace delimited token), and then attempts to parse the correct amount of arguments.

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

#define WHITESPACE "\t\n "

static void parse(const char *command)
{
    /* skip leading whitespace */
    command += strspn(command, WHITESPACE);

    /* find the length of the command token */
    size_t cmdl = strcspn(command, WHITESPACE);

    /* empty command */
    if (!cmdl)
        return;

    /* find the start of the arguments, skip intervening whitespace */
    const char *args = command + cmdl;
    args += strspn(args, WHITESPACE);

    fprintf(stderr, "DEBUG: <%.*s> [%s]\n", (int) cmdl, command, args);
    
    /* compare only up to `cmdl` number of characters.
     * this pattern allows for commands of varying length (e.g., "open", "L"), 
     * not just single characters (e.g., 'O', 'L').
     */
    if (0 == strncmp("A", command, cmdl)) {
        char name[64];
        float price;

        if (2 == sscanf(args, "%63s%f", name, &price))
            printf("Command `A` got <%s>, <%.2f>.\n", name, price);
        else
            fprintf(stderr, "Invalid input for `A`.\n");
    } else if (0 == strncmp("B", command, cmdl)) {
        char name[64];
        int count;

        if (2 == sscanf(args, "%63s%d", name, &count) && count > 0)
            printf("Command 'B' got <%s>, <%d>.\n", name, count);
        else
            fprintf(stderr, "Invalid input for `B`.\n");
    }
}

int main(void)
{
    char line[4096];

    while (1) {
        printf("> ");

        if (!fgets(line, sizeof line, stdin))
            break;

        /* remove newline from buffer */
        line[strcspn(line, "\n")] = 0;

        parse(line);
    }
}

Usage:

$ ./a.out 
> A Tetris 5.99
DEBUG: <A> [Tetris 5.99]
Command `A` got <Tetris>, <5.99>.
> B Tetris 3
DEBUG: <B> [Tetris 3]
Command 'B' got <Tetris>, <3>.
> A foo bar
DEBUG: <A> [foo bar]
Invalid input for `A`.
> B qux
DEBUG: <B> [qux]
Invalid input for `B`.
>

1. Note that an unbounded %s is equally as dangerous as gets (a function so flawed it was removed from the language), as it will attempt to read an unlimited amount of data from the stream (or string in the case of sscanf) into the buffer, leading to buffer overflows.

You must always specify a maximum field width, when using %s (or %[), which should be at most the size of your buffer minus one (leaving room for the null-terminating byte). The examples above showcase this.


2. As an edge-case, the user can potentially enter a very long line of text (e.g., longer than the buffer you provide to fgets), in which case fgets returns early before reading a newline. This will upset a line-based input approach if steps aren't taken to clear excess data after this occurs. Basically, a robust solution ensures fgets read a newline (e.g., NULL != strchr(buffer, '\n')), and otherwise clears the input buffer until a newline is encountered.

Oka
  • 23,367
  • 6
  • 42
  • 53
  • Thank you so much for your reply! I'm starting to think I'm way over my head here x) – Ronja Oksanen Aug 23 '23 at 13:18
  • The example above is rather dense, so take your time reading through it. Any time it stops making sense, refer to the documentation for the function being used. I have added links to documentation (with examples) for every function I use in the example (and a few more). – Oka Aug 23 '23 at 13:50