Simple C program to read a file line by line

10,780

In C you have character oriented input functions (e.g. getchar, fgetc), you have formatted input functions (e.g. the scanf family) and then you have line oriented input functions. (e.g. fgets and POSIX getline). When you are reading lines of data, line oriented input functions are the proper tool for the job. (taking user input with scanf has many pitfalls that new (and even not so new) C programmers fall into)

All line oriented functions read and include the '\n' in the buffer they fill. You can, and should, remove the newline from the resulting buffer if it will be used later on in your code. A simple

size_t n = strlen (buf);
if (buf[n-1] == '\n') 
    buf[--n] = 0;

is all you need to overwrite the trailing '\n' with a nul-terminating character. If you are just printing the line immediately and not storing it for later use, then it's not worth removing the newline (just account for it in your output format string).

Putting those pieces together, you can read each line, handle the first by simply outputting it, and for each remaining line, parse the time (presumable some elapsed time) from the full string read by fgets with sscanf and format the output as you specify. E.g.

#include <stdio.h>

#define MAXC 64     /* define constants, don't use magic number in code */

int main (int argc, char **argv) {

    char buf[MAXC] = "";    /* buffer to hold each line -- size as reqd */
    int line = 1;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    while (fgets (buf, sizeof buf, fp)) {   /* read each line in file  */
        char et[MAXC] = "";                 /* buffer for holding time */
        if (line == 1)                      /* if 1st line, just print */
            printf ("%d : %s", line, buf);  /* note: \n included by fgets */
        else {
            if (sscanf (buf, "%s", et) != 1) { /* parse up to first whitespace */
                fprintf (stderr, "error: invalid conversion, line %d\n", line);
                return 1;
            }
            printf ("%d : %s\n", line, et); /* output elapsed time only  */
        }
        line++;                             /* increment line count */
    }

    if (fp != stdin) fclose (fp);           /* close file if not stdin */

    return 0;
}

note: you should protect against buffer overrun on parse by including a field-width specifier in the sscanf format string (e.g. sscanf (buf, "%63s", et), and that is one place that all you can do is include magic numbers in your code because there is no way to directly specify a variable width specifier for sscanf -- unless you creatively use sprintf to create the format string ahead of time -- but that's for another day..

Example Input File

$ cat dat/et.txt
My test file
00:19.1 123456
00:35.4 testing whitespace end

Example Use/Output

$ ./bin/et <dat/et.txt
1 : My test file
2 : 00:19.1
3 : 00:35.4

Look things over and let me know if you have any further questions.

(note: I take the filename as the first argument to the program, or read from stdin if no filename is given. C provides for command line arguments -- use them. It's fine to prompt for input if needed, otherwise, its far easier just to specify arguments on the command line :)

Share:
10,780
Collin
Author by

Collin

Updated on June 26, 2022

Comments

  • Collin
    Collin almost 2 years

    What I would like to do is read the whole first line of the file but then after the first line only read the following lines until whitespace is hit. My end goal is to ask the user what line they want to edit by adding/subtracting time to said line.

    Sample File

    My test file
    00:19.1 123456
    00:35.4 testing whitespace end
    

    Desired Output

    1: My test file
    2: 00:19.1
    3: 00:35.4
    

    Code:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        FILE *fptr1, *fptr2;
    
        char filechar[40];
        char c[50];
        int line_number = 1;
        int replace_line, temp = 1;
    
        printf("Please enter a file name: ");
        scanf("%s", &filechar);
    
        if ((fptr1 = fopen(filechar, "r")) == NULL)
        {
            printf("Error locating desired file");
            exit(1);
        }
    
        c = getc(fptr1);
        while (c != EOF)
        {
            //printf("%d: %c",line_number, c);
            printf("%s",c);
            c = getc(fptr1);
            //line_number++;
        }
        return 0;
    }
    
  • Collin
    Collin almost 7 years
    I get two errors while using this method. The first one being undefined reference to getline, second being ld retured 1 exit status.
  • Collin
    Collin almost 7 years
    Thank you this works to an extent, can you explain the invalid conversion line you added in? Four lines into my test file I errored out. Line 1: My test file Line 2: 00:19.1 009-021 Line 3: 00:35.4 017-003 018-003 Line 4: 00:35.5 017-000 018-000
  • David C. Rankin
    David C. Rankin almost 7 years
    Sure, it is just a validation that one conversion to %s was preformed by sscanf. (essentially I'm just checking the return of sscanf) If for whatever reason sscanf failed (which with the %s is rather unlikely), you don't want to keep processing with et as that would be Undefined Behavior. C doesn't provide any safety nets, so it's up to you to validate all input, conversions, etc... :) Recall, all scanf functions return the number of successful conversion performed.
  • Collin
    Collin almost 7 years
    Okay I understand now. Thank you for all the help!
  • David C. Rankin
    David C. Rankin almost 7 years
    Glad I could help. C is a fantastically flexible language that provides you total control over the machine at a low-level. What's the saying ("With great power comes great responsibility...") Nothing could be truer with C. Validate, validate, validate... :)
  • Collin
    Collin almost 7 years
    Is there a way to write over the line I am reading in the while loop while leaving the characters after the whitespace untouched?
  • David C. Rankin
    David C. Rankin almost 7 years
    Yes, you can open the file in "r+" mode and manipulate the file position indicator to backup strlen(buf) chars and fputc (or fprintf) some mask char string like '#' (or whatever), but you must be very very careful that you do not change the byte-count per-line, otherwise you corrupt the file. See man fopen regarding the ANSI C requirement that "a file positioning function intervene between output and input". (in fact, it is always a good idea to consult the man page any time you use a function you are not completely familiar with :)