0

I, for the life of me, cannot be at peace with c strings and input/output.

For my program I simply enter a string and it gets processed in the following code: (tmpstring and ch are already defined)

For my incomning input, I write in terminal: echo "test" | ./program

    int main(int argc, char *argv[]) {
    char tmpstring[2048];
    int ch;
    int r;
    int c;

    fgets(tmpstring, sizeof tmpstring, stdin);
    while((ch = fgetc(stdin))!= EOF && ch != '\n');
    tmpstring[strlen(tmpstring)-1]='\0';

    strncpy(opponent, tmpstring+1, strlen(tmpstring+1));

    move();

Inside of move();

    char buffer[2048]={0};
    int r, c;
    r=0; c=0;
    printf("Your move (row column):");
    if((fgets(buffer, sizeof buffer, stdin)==NULL) || ((sscanf(buffer,"%d %d", &r, &c))!=2)){
            printf("Invalid input. Please insert two numbers separated by whitespace.\n");
            exit(1);
    }
    //continues

Executing this goes straight into the invalid input without asking for more input. I've read all around about how you shouldn't clear stdin (and that it's impossible) but I really don't know what to do. I clearly tried to "dump" stdin with the second part of the while loop. I've changed the && after the first condition to an || in the while loop. No change. Overall, how can I ask for more input after already used fgets?

*edit: more code and separated the original while loop

boses
  • 400
  • 4
  • 12
  • Suggestion: separate stuff into different statements. In your code above you have `fgets` and `fgetc` in the same statement (also `fgets` and `sscanf` later). – pmg Mar 10 '11 at 22:21
  • I'm having trouble reproducing your problem. I simply call `fgets(tmpstr, sizeof(tmpstr), stdin); fgets(buffer, sizeof(buffer), stdin); sscanf(buffer, "%d %d", &a, &b);` without any loops and it works. Could you maybe show more of your code? – dialer Mar 10 '11 at 22:30
  • 1
    With `echo "test" | ./program` the stream `stdin` gets into end-of-file condition right after the first `fgets`. The while loop terminates because `ch` is assigned `EOF` and in the `move()` function `fgets()` returns NULL because `stdin` is in end-of-file condition. – pmg Mar 10 '11 at 22:57
  • OP: after removing the while loop and instead of adding input via piping but rather asking for input in the beginning, it worked. So then I must ask, you can never repeat fgets if the stdin doesn't have a '\n'? – boses Mar 10 '11 at 23:08
  • @pmg indeed. You should be able to use `feof(stdin) && clearerr(stdin)` to reset the EOF indicator. Furthermore, you could basically leave out the while loop since it doesn't contribute anything to the program as I see it. – dialer Mar 10 '11 at 23:13
  • @boses: the pipe from the shell includes a `'\n'`. `fgets()` is designed to read lines (sequences of characters ending in, and including, a `'\n'`). Your problem is that your input contained one single `'\n'` before it went into end-of-file condition. – pmg Mar 10 '11 at 23:18
  • @boses Your comment just popped up: If you provide stdin with `echo ... |` or with `./program < ./file`, the design idea was that the given input covers _everything_ the program ever asks for - as opposed to prompting the user for direct keyboard input, which doesn't ever reach EOF. Combining both is rather deprecated as I've been taught. – dialer Mar 10 '11 at 23:22
  • Thank you both so much. Lesson learned: echo "string" is not the same as inputting "string" through a keyboard. – boses Mar 10 '11 at 23:32
  • 2
    Your `strncpy` doesn't copy the terminating `NUL`. Never ever use `strncpy`. – Jim Balter Mar 11 '11 at 00:02
  • @boses it's the same as inputting _string_ and then hitting enter and then entering an EOF (ctrl-D on POSIX, ctrl-Z on Windows). – Jim Balter Mar 11 '11 at 00:07

1 Answers1

0

This kind of problem has been discussed elsewhere.
I will copy a part of my own post, from here:

Max string length using scanf -> ANSI C

  • It is defined a function my_scanf() with variable number of parameters, by invoking the stdarg.h library, joint to a combination of fgets() and vsscanf().
  • That function is designed to handle in a right manner combinations of fgets() and sscanf() (actually, we have to use vsscanf() in order to properly process the list of arguments).
  • The input is read up to an upper limit of characters. if the input has more than this limit, it is truncated. The '\n' are handled correctly. The function returns the same value as scanf() would return. So, you can use my_scanf() instead.

Here you have the code:

#include <stdio.h>
#include <stdarg.h>

int my_scanf(const char* fmt, const unsigned int maxbuff, ...) {
    va_list ptr;
    int ret;

    if (maxbuff <= 0)
       return EOF; /* Bad size for buffer[] */

    char buffer[maxbuff+1];
    buffer[maxbuff-1] = '\0';  /* Quick buffer cleaning... */

    if (fgets(buffer, maxbuff+1, stdin) == NULL)
       return EOF; /* Error detected */
    else {
        if ((buffer[maxbuff-1] != '\n') && (buffer[maxbuff-1] != '\0'))
            /* Condition logically equivalent to:
                   fgets() has not reached an '\n'
            */
            while (getchar() != '\n')
               ; /* "Flushing" stdin... */

        va_start(ptr, maxbuff);
        ret = vsscanf(buffer, fmt, ptr);
        va_end(ptr);
        return ret;
    }    
}

#define MAXBUFF 20
int main(void) {
   int x; 
   float z;
   int scanf_ret = my_scanf("%d %g", MAXBUFF, &x, &z);
   printf("\nTest:\n x == %d\n z == %g\n scanfret == %d", x, z, scanf_ret);
   getchar();   
   return 0;   
}

The function my_scanf() has the prototype

int my_scanf(const char* fmt, const int maxbuff, ...);
  • It accepts a format string fmt that behaves in the same way as any other scanf()-like does.
  • The 2nd parameter is the maximum number of chars that will be effectively accepted from the standard input (keyboard).
  • The return value is an int, which is EOF if maxbuff has no sense, or well some input error happened. If a non-negative value is returned, it is the same that would be returned by the standard functions sscanf() or vsscanf().

    1. Inside the function, maxbuff is incremented in 1, because fgets() makes some room for an additional '\0' character.
    2. Non-positive values of maxbuff are immediatelly discarded.
    3. fgets() will read a string from stdin (keyboard) with room for at most maxbuff characters, including '\n'.
    4. If the user has entered a very long string, then it will be truncated, and some kind of "flush" mechanism is necessary in order to discard all the characters to the next '\n' (ENTER). If not, the next keyboard reading could have older characters, not desired at all.
      • The condition for "flushing" is that fgets() has not reached '\n' after reading stdin.
      • This is true if, and only if, buffer[maxbuff - 1] is not equal to '\0' nor '\n'. (Check it!)
    5. Finally, an appropiate (and standard) combination of stdarg.h macros and the function vsscanf() is employed to process the variable list of parameters.
Community
  • 1
  • 1
pablo1977
  • 4,281
  • 1
  • 15
  • 41