简体   繁体   中英

Reading c file line by line using fgetc()

This is how I've done it but I'm not sure this is the preferred idiom:

FILE *fp = fopen(argv[0], "r");
// handle fopen() returning NULL

while (!feof(fp)) {
    char buffer[80]; // statically allocated, may replace this later with some more sophisticated approach
    int num_chars = 0;

    for (int ch = fgetc(fp); ch != EOF && ch != '\n'; ch = fgetc()) {
        buffer[num_chars++] = ch;
    }

    // null-terminate the string
    buffer[num_chars] = '\0';

    printf("%s\n", buffer);
}

Is this okay, any suggestions to improve this?

If you are not going to use fgets() (perhaps because you want to remove the newline, or you want to deal with "\\r" , "\\n" or "\\r\\n" line endings, or you want to know how many characters were read), you can use this as a skeleton function:

int get_line(FILE *fp, char *buffer, size_t buflen)
{
    char *end = buffer + buflen - 1; /* Allow space for null terminator */
    char *dst = buffer;
    int c;
    while ((c = getc(fp)) != EOF && c != '\n' && dst < end)
        *dst++ = c;
    *dst = '\0';
    return((c == EOF && dst == buffer) ? EOF : dst - buffer);
}

It recognizes only newline as the end of line; it drops the newline. It does not overflow the buffer; it does not discard excess characters, so if called upon to read a very long line, it will read the line in chunks; it returns the number of characters read. If you need to distinguish between overflow and a line that happens to be the length of the buffer - 1, then you probably need to preserve the newline - with consequential changes in the code:

int get_line(FILE *fp, char *buffer, size_t buflen)
{
    char *end = buffer + buflen - 1; /* Allow space for null terminator */
    char *dst = buffer;
    int c;
    while ((c = getc(fp)) != EOF && dst < end)
    {
        if ((*dst++ = c) == '\n')
            break;
    }
    *dst = '\0';
    return((c == EOF && dst == buffer) ? EOF : dst - buffer);
}

There are endless minor variants on this, such as discarding any excess characters if the line has to be truncated. If you want to handle DOS, (old) Mac or Unix line endings, then borrow a leaf out of the CSV code from "The Practice of Programming" by Kernighan & Pike (an excellent book) and use:

static int endofline(FILE *ifp, int c)
{
    int eol = (c == '\r' || c == '\n');
    if (c == '\r')
    {
        c = getc(ifp);
        if (c != '\n' && c != EOF)
            ungetc(c, ifp);
    }
    return(eol);
}

Then you can use that in place of the c != '\\n' test:

int get_line(FILE *fp, char *buffer, size_t buflen)
{
    char *end = buffer + buflen - 1; /* Allow space for null terminator */
    char *dst = buffer;
    int c;
    while ((c = getc(fp)) != EOF && !endofline(fp, c) && dst < end)
        *dst++ = c;
    *dst = '\0';
    return((c == EOF && dst == buffer) ? EOF : dst - buffer);
}

The other alternative way of dealing with the whole process is using fread() and fwrite() :

void copy_file(FILE *in, FILE *out)
{
    char buffer[4096];
    size_t nbytes;
    while ((nbytes = fread(buffer, sizeof(char), sizeof(buffer), in)) != 0)
    {
        if (fwrite(buffer, sizeof(char), nbytes, out) != nbytes)
            err_error("Failed to write %zu bytes\n", nbytes);
    }
}

In context, you'd open the file and check it for validity, then call:

copy_file(fp, stdout);

If you need every char in order to inspect it or modify or whatever else then use fgets. For everything else, use fgets.

 fgets (buffer, BUFFER_SIZE, fp);

Note that fgets will read until a new line or EOF is reached (or the buffer is full of course). New line character "\\n" is also appended to the string if read from the file. Null character is also appended.

fgets returns :

On success, the function returns the same str parameter.
If the End-of-File is encountered and no characters have been read, the contents of str remain unchanged and a null pointer is returned.
If an error occurs, a null pointer is returned.
Use either ferror or feof to check whether an error happened or the End-of-File was reached.

You're risking buffer overflow if the user inputs 80 characters or more.

I'm with ThiefMaster: you should use fgets() , instead. Read the input into a buffer that's larger than any legitimate input and then check that the last character is a newline.

Unless you're hoping to get a ultra-high efficient way to set the number of characters read, use fgets() .

Replacing your example with a similar but different simple fgets() , you "lose" the num_chars variable.

fgets(buffer, sizeof buffer, stdin);
fputs(buffer, stdout); /* buffer contains a '\n' */

If you need to remove the last '\\n'

buffer[0] = 0;
if (!fgets(buffer, sizeof buffer, stdin)) /* error or eof */;
num_chars = strlen(buffer);
if (num_chars && (buffer[num_chars - 1] == '\n')) buffer[--num_chars] = 0;
puts(buffer); /* add a '\n' to output */

If the strings are really humongous (like 42 Mega bytes worth), you may be better off reading character by character and keeping count with num_chars than using fgets first and strlen later.

No linesize-limit und strictly C89 (your code is only C99) like:

FILE *fp = fopen(argv[0], "r");
size_t len=1;
char c, *buffer=calloc(1,1);
/* handle fopen() returning NULL*/
while( c=fgetc(fp),!feof(fp) )
  if( c=='\n' )
  {
    puts(buffer);
    len=1;
    *buffer=0;
  }
  else
    strncat(buffer=realloc(buffer,++len),&c,1); /* check for NULL needed */

puts(buffer);
free(buffer);
fclose(fp);
#include<stdio.h>
void main()
{
    FILE *fp;
    char c;
    int ch=0,w=0,l=0;
    fp=fopen("c:\read.txt","w");
    clrscr();
    if(fp==NULL)
    {
        printf("\n\n\tDOES NOT EXIXST");
        getch();
        exit(0);
    }
    while(!feof(fp))
    {
        c=fgetc(fp);

        ch++;
        if(c==' ')
        {
            w++;
        }
        if(c=='\n')
        {
            l++;
            w++;
        }
    }

    printf("\n\n\tTOTAL CHAR = %d\n\n\tTOTAL WORDS = %d\n\n\tTOTAL LINES = %d",ch,w,l);

}

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