Code of the Day
IntermediateFile I/O

Lab: CSV parser

Parse a comma-separated value file in C using fgets and sscanf — applying file I/O, string processing, and error handling together.

Lab · optionalCIntermediate35 min
Recommended first

CSV (Comma-Separated Values) is one of the most common data exchange formats. Parsing it in C requires combining file reading, string splitting, and type conversion. This lab builds a parser for a simple CSV format with a known schema.

The input file

Create students.csv:

name,grade,gpa
Alice,11,3.8
Bob,10,3.2
Carol,12,3.9
David,11,2.7
Eve,12,4.0

Goal

Write csv_parser.c that reads students.csv and:

  1. Skips the header line.
  2. Parses each data row into a struct.
  3. Prints all students with GPA above a threshold.
  4. Prints the average GPA.

The struct

typedef struct {
    char  name[32];
    int   grade;
    double gpa;
} Student;

Approach

Read lines with fgets. Skip the first (header). For each subsequent line, strip the newline, then use sscanf to parse:

sscanf(line, "%31[^,],%d,%lf", student.name, &student.grade, &student.gpa);

%31[^,] reads up to 31 non-comma characters (the name). %d reads the grade. %lf reads the GPA as a double.

Worked solution

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

typedef struct {
    char   name[32];
    int    grade;
    double gpa;
} Student;

int main(void) {
    FILE *fp = fopen("students.csv", "r");
    if (!fp) {
        perror("fopen");
        return EXIT_FAILURE;
    }

    char line[256];
    /* Skip header */
    if (!fgets(line, sizeof(line), fp)) {
        fprintf(stderr, "Empty file\n");
        fclose(fp);
        return EXIT_FAILURE;
    }

    Student students[100];
    int count = 0;

    while (fgets(line, sizeof(line), fp) && count < 100) {
        /* Strip trailing newline */
        size_t len = strlen(line);
        if (len > 0 && line[len-1] == '\n') { line[len-1] = '\0'; }

        Student s;
        int parsed = sscanf(line, "%31[^,],%d,%lf",
                            s.name, &s.grade, &s.gpa);
        if (parsed == 3) {
            students[count++] = s;
        } else {
            fprintf(stderr, "Skipping malformed line: %s\n", line);
        }
    }

    fclose(fp);

    /* Print students with GPA > 3.5 */
    printf("High achievers (GPA > 3.5):\n");
    for (int i = 0; i < count; i++) {
        if (students[i].gpa > 3.5) {
            printf("  %-10s grade %d  GPA %.1f\n",
                   students[i].name, students[i].grade, students[i].gpa);
        }
    }

    /* Average GPA */
    double total = 0.0;
    for (int i = 0; i < count; i++) {
        total += students[i].gpa;
    }
    printf("\nAverage GPA: %.2f (across %d students)\n",
           count > 0 ? total / count : 0.0, count);

    return EXIT_SUCCESS;
}

Expected output:

High achievers (GPA > 3.5):
  Alice      grade 11  GPA 3.8
  Carol      grade 12  GPA 3.9
  Eve        grade 12  GPA 4.0

Average GPA: 3.52 (across 5 students)

Extension: quoted fields

Real CSV files allow commas inside quoted fields: "Smith, John",11,3.8. Extend the parser to handle quoted names by checking whether the first character is " and reading until the closing ".

Extension: dynamic allocation

The current solution uses a fixed-size array of 100 students. Replace it with a DynArray of Student * using malloc — calling realloc as needed. This reuses the growable buffer pattern from the Memory Management lab.

What you practised

  • Reading a file line by line with fgets
  • Parsing structured text with sscanf
  • Error handling for malformed input
  • Computing aggregate statistics from a collection of structs

You have completed the Intermediate tier. The Advanced tier begins with the C compilation model — understanding exactly what gcc does in four stages, and how to build multi-file projects with Makefiles.

Finished reading? Mark it complete to track your progress.

On this page