3

Is the following pattern ok in C to get a string up until a newline?

int n = scanf("%40[^\n]s", title);
getchar();

It seems to work in being a quick way to strip off the trailing newline, but I'm wondering if there are shortcomings I'm not seeing here.

dreamcrash
  • 47,137
  • 25
  • 94
  • 117
David542
  • 104,438
  • 178
  • 489
  • 842

3 Answers3

3

The posted code has multiple problems:

  • the s in the format string is not what you think it is: the specification is %40[^\n] and the s will try and match an s in the input stream, which may occur after 40 bytes have been stored into title.

  • scanf() will fail to convert anything of the pending input is a newline, leaving title unchanged and potentially uninitialized

  • getchar() will not necessarily read the newline: if more than 40 characters are present on the line, it will just read the next character.

If you want to read a line, up to 40 bytes and ignore the rest of the line up to and including the newline, use this:

    char title[41];
    *title = '\0';
    if (scanf("%40[^\n]", title) == EOF) {
        // end of file reached before reading anything, handle this case
    } else {
        scanf("%*[^\n]"); // discard the rest of the line, if any
        getchar();        // discard the newline if any (or use scanf("%1*[\n]"))
    }

It might be more readable to write:

    char title[41];
    int c, len = 0;
    while ((c = getchar()) != EOF && c != '\n') {
        if (len < 40)
            title[len++] = c;
    }
    title[len] = '\0';
    if (c == EOF && len == 0) {
        // end of file reached before reading a line
    } else {
        // possibly empty line of length len was read in title
    }

You can also use fgets():

    char title[41];

    if (fgets(title, sizeof title, stdin) {
        char *p = strchr(title, '\n');
        if (p != NULL) {
            // strip the newline
            *p = '\0';
        } else {
            // no newline found: discard reamining characters and the newline if any
            int c;
            while ((c = getchar()) != EOF && c != '\n')
                continue;
        }
    } else {
        // at end of file: nothing was read in the title array
    }
chqrlie
  • 131,814
  • 10
  • 121
  • 189
1

Previous note, the s should be removed, it's not part of the specifier and is enough to mess up your read, scanf will try to match an s character against the string you input past the 40 characters, until it finds one the execution will not advance.

To answer your question using a single getchar is not the best approach, you can use this common routine to clear the buffer:

int n = scanf(" %40[^\n]", title);

int c;
while((c = getchar()) != '\n' && c != EOF){}

if(c == EOF){
   // in the rare cases this can happen, it may be unrecoverable
   // it's best to just abort
   return EXIT_FAILURE;
}
//...

Why is this useful? It reads and discards all the characters remaing in the stdin buffer, regardless of what they are.

In a situation when an inputed string has, let's say 45 characters, this approach will clear the stdin buffer whereas a single getchar only clears 1 character.

Note that I added a space before the specifier, this is useful because it discards all white spaces before the first parseable character is found, newlines, spaces, tabs, etc. This is usually the desired behavior, for instance, if you hit Enter, or space Enter it will discard those and keep waiting for the input, but if you want to parse empty lines you should remove it, or alternatively use fgets.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
0

There are a number of problems with your code like n never being used and wrong specifier for scanf.

The better approach is to use fgets. fgets will also read the newline character (if present before the buffer is full) but it's easy to remove.

See Removing trailing newline character from fgets() input

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63