简体   繁体   中英

How to take a line input in C?

I was trying to take a full line input in C. Initially I did,

char line[100] // assume no line is longer than 100 letters.
scanf("%s", line);

Ignoring security flaws and buffer overflows, I knew this could never take more than a word input. I modified it again,

scanf("[^\n]", line);

This, of course, couldn't take more than a line of input. The following code, however was running into infinite loop,

while(fscanf(stdin, "%[^\n]", line) != EOF)
{
    printf("%s\n", line);
} 

This was because, the \\n was never consumed, and would repeatedly stop at the same point and had the same value in line . So I rewrote the code as,

while(fscanf(stdin, "%[^\n]\n", line) != EOF)
{
    printf("%s\n", line);
}

This code worked impeccably(or so I thought), for input from a file. But for input from stdin , this produced cryptic, weird, inarticulate behavior. Only after second line was input, the first line would print. I'm unable to understand what is really happening.

All I am doing is this. Note down the string until you encounter a \\n , store it in line and then consume the \\n from the input buffer. Now print this line and get ready for next line from the input. Or am I being misled?

At the time of posting this question however, I found a better alternative,

while(fscanf(stdin, "%[^\n]%*c", line) != EOF)
{
    printf("%s\n", line);
}

This works flawlessly for all cases. But my question still remains. How come this code,

while(fscanf(stdin, "%[^\n]\n", line) != EOF)
{
    printf("%s\n", line);
}

worked for inputs from file, but is causing issues for input from standard input?

Use fgets() . @FredK

char buf[N];
while (fgets(buf, sizeof buf, stdin)) {
  // crop potential \n if desired.
  buf[strcspn(buf, "\n")] = '\0'; 
  ...
}

There are to many issues trying to use scanf() for user input that render it prone to mis-use or code attacks.

// Leaves trailing \n in stdin
scanf("%[^\n]", line)

// Does nothing if line begins with \n. \n remains in stdin
// As return value not checked, use of line may be UB.
// If some text read, consumes \n and then all following whitespace: ' ' \n \t etc.
//    Then does not return until a non-white-space is entered.
//    As stdin is usually buffered, this implies 2 lines of user input.
// Fails to limit input.
scanf("%[^\n]\n", line)

// Does nothing if line begins with \n. \n remains in stdin
// Consumes 1 char after `line`, even if next character is not a \n
scanf("%99[^\n]%*c", line)

Check against EOF is usual the wrong check. @Weather Vane The following, when \\n is first entered, returns 0 as line is not populated. As 0 != EOF , code goes on to use an uninitialized line leading to UB.

while(fscanf(stdin, "%[^\n]%*c", line) != EOF)

Consider entering "1234\\n" to the following. Likely infinite loop as first fscanf() read "123", tosses the "4" and the next fscanf() call gets stuck on \\n.

while(fscanf(stdin, "%3[^\n]%*c", line) != EOF)

When checking the results of *scanf() , check against what you want , not against one of the values you do not want. (But even the following has other troubles)

while(fscanf(stdin, "%[^\n]%*c", line) == 1)

About the closest scanf() to read a line :

char buf[100];
buf[0] = 0;
int cnt = scanf("%99[^\n]", buf);
if (cnt == EOF) Handle_EndOfFile();
// Consume \n if next stdin char is a \n
scanf("%*1[\n]");
// Use buf;

while(fscanf(stdin, "%[^\\n]%*c", line) != EOF)
worked for inputs from file, but is causing issues for input from standard input?

Posting sample code and input/data file would be useful. With modest amount of code posted, some potential reasons.

line overrun is UB
Input begins with \\n leading to UB
File or stdin not both opened in same mode. \\r not translated in one.


Note: The following fails when a line is 100 characters. So meeting the assumption cal still lead to UB.

char line[100] // assume no line is longer than 100 letters.
scanf("%s", line);

Personally, I think fgets() is badly designed. When I read a line, I want to read it in whole regardless of its length (except filling up all RAM). fgets() can't do that in one go. If there is a long line, you have to manually run it multiple times until it reaches the newline. The glibc-specific getline() is more convenient in this regard. Here is a function that mimics GNU's getline():

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

long my_getline(char **buf, long *m_buf, FILE *fp)
{
    long tot = 0, max = 0;
    char *p;
    if (*m_buf == 0) { // empty buffer; allocate
        *m_buf = 16;   // initial size; could be larger
        *buf = (char*)malloc(*m_buf); // FIXME: check NULL
    }
    for (p = *buf, max = *m_buf;;) {
        long l, old_m;
        if (fgets(p, max, fp) == NULL)
            return tot? tot : EOF; // reach end-of-file
        for (l = 0; l < max; ++l)
            if (p[l] == '\n') break;
        if (l < max) { // a complete line
            tot += l, p[l] = 0;
            break;
        }
        old_m = *m_buf;
        *m_buf <<= 1; // incomplete line; double the buffer
        *buf = (char*)realloc(*buf, *m_buf); // check NULL
        max = (*m_buf) - old_m;
        p = (*buf) + old_m - 1; // point to the end of partial line
    }
    return tot;
}

int main(int argc, char *argv[])
{
    long l, m_buf = 0;
    char *buf = 0;
    while ((l = my_getline(&buf, &m_buf, stdin)) != EOF)
        puts(buf);
    free(buf);
    return 0;
}

I usually use my own readline() function. I wrote this my_getline() a moment ago. It has not been thoroughly tested. Please use with caution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM