Laboratory: Files

Gábor Horváth 2024.10.31.

The goal of the lab is to practice file handling in C

Preparation for lab:

  • Review lecture topics on files and strings.

1. Convert to upper case

Write a program that converts all lower case latin letters in a text file to uppercase. Write the result to an other file called "output.txt". Display an error message in case opening the files was not successful.

To test the program, download this file, copy it to the project folder, and execute the program. Then, open "output.txt" with notepad, and check whether its content is correct.

Hint

Detecting and handling the end of file properly is often a critical issue in a file processing program. There are two options to choose from:

  1. Based on the return value of fscanf: Remember that fscanf returns the number of data successfully obtained from the file - if it is less than expected, than probably the end of file has been reached.
  2. Based on feof: The feof function can also be used to detect the end of a file. But there is a catch. feof returns false even after reading the last data from the file. It becomes true only after the next read attempt, that failed due to the end of file. Thus, its value has to be checked after every read attempt and the data processing needs to be stopped when it returns false.

Solution

This solution uses the return value of fscanf to detect the end of file:

#include <stdio.h>
#include <ctype.h>

int main() {
    FILE *fin, *fout;
    char c;

    fin = fopen("lorem.txt", "r");
    if (fin == NULL) {
        printf("Cannot open input file\n");
        return 1;
    }

    fout = fopen("output.txt", "w");
    if (fout == NULL) {
        printf("Cannot open output file\n");
        return 2;
    }

    while (fscanf(fin, "%c", &c) == 1) {
        if (islower(c))
            c = toupper(c);
        fprintf(fout, "%c", c);
    }

    fclose(fin);
    fclose(fout);

    return 0;
}

2. Count lines

Write a program that counts the number of lines in a text file.

To test the program, download this file, copy it to the project folder, and execute the program. The number of lines in this file is 63.

Solution

#include <stdio.h>

int main() {
    FILE *f;
    char c;
    int count = 0;

    f = fopen("lorem.txt", "r");
    if (f == NULL) {
        printf("Cannot open input file\n");
        return 1;
    }

    while (fscanf(f, "%c", &c) == 1) {
        if (c == '\n')
            count++;
    }

    printf("The number of lines is: %d\n", count + 1);
    fclose(f);

    return 0;
}

3. Line wrap

Write a program that opens a text file and breaks all lines wider than 20 charaters. The output should be written to "output.txt".

To test the program, download this file, copy it to the project folder, and execute the program. Then, open "output.txt" with notepad, and check whether its content is correct.

Solution

#include <stdio.h>

int main() {
    FILE *fin, *fout;
    char c;
    int line_len = 0;

    fin = fopen("lorem.txt", "r");
    if (fin == NULL) {
        printf("Cannot open input file\n");
        return 1;
    }

    fout = fopen("output.txt", "w");
    if (fout == NULL) {
        printf("Cannot open output file\n");
        return 2;
    }

    while (fscanf(fin, "%c", &c) == 1) {
        if (c == '\n') {
            line_len = 0;
        }
        else if (line_len == 20) {
            fprintf(fout, "\n");
            line_len = 0;
        }
        fprintf(fout, "%c", c);
        line_len++;
    }

    fclose(fin);
    fclose(fout);

    return 0;
}

4. Job assignments in a file

Write a C program tho manage job assignments. Each job assignment has

  • a unique identifier (an integer number),
  • the name of the employee who has to do the job (max. length: 100 characters),
  • the description of the job (max. length: 200 characters),
  • and a status, which is a 1/0 value indicating that the job is done or not done yet.

The file containing job assignments starts with an integer number, representing the total number of jobs in the file, then the members of the structure follow each other in separate lines.

The C code below creates a structure to represent job assignments, and loads job assignments from file "jobs.txt" into a dynamic array.

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

typedef struct {
    int id;
    char name[101];
    char descr[201];
    int status;
} job;

int main () {
    FILE* infile;   // file handle
    job* a;         // start address of the dynamic array    
    int N;          // number of elements in the dynamic array
    int i;          // auxiliary variable for loops
    
    // open file
    infile = fopen("jobs.txt","r");
    if (infile==NULL) {
        printf("Cannot open jobs.txt\n");
        return 1;
    }
    
    // load the content of the file into a dynamic array
    fscanf(infile, "%d", &N);   // first integer is the number of items
    a = (job*)malloc(sizeof(job)*N);
    for (i = 0; i < N; i++) {
        fscanf(infile, "%d\n", &a[i].id);
        fgets(a[i].name, 101, infile);       // we use fgets to allow space characters in the name
        a[i].name[strlen(a[i].name)-1] = '\0'; // last newline character is deleted
        fgets(a[i].descr, 201, infile);        
        a[i].descr[strlen(a[i].descr)-1] = '\0';
        fscanf(infile, "%d\n", &a[i].status);
    }
    fclose(infile);
 
    free(a);
    return 0;
}

Note that we used fgets to read the name and the description, to allow spaces. Since fgets keeps the newline character and the end of the line, we had to remove it manually.

The tasks are as follows.

  • Write a function to print all jobs assigned to "Joe"
  • Set the status of all jobs assigned to "Joe" to 1.
  • Save the modified data to "jobs2.txt".

To test the program, download this file, copy it to the project folder, and execute the program. Then, open "jobs2.txt" with notepad, and check whether its content is correct.

Solution

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

typedef struct {
    int id;
    char name[101];
    char descr[201];
    int status;
} job;

int main () {
    FILE *infile, *outfile;   // file handle
    job* a;         // start address of the dynamic array    
    int N;          // number of elements in the dynamic array
    int i;          // auxiliary variable for loops
    
    // open file
    infile = fopen("jobs.txt","r");
    if (infile==NULL) {
        printf("Cannot open jobs.txt\n");
        return 1;
    }
    
    // load the content of the file into a dynamic array
    fscanf(infile, "%d", &N);   // first integer is the number of items
    a = (job*)malloc(sizeof(job)*N);
    for (i = 0; i < N; i++) {
        fscanf(infile, "%d\n", &a[i].id);
        fgets(a[i].name, 101, infile);       // we use fgets to allow space characters in the name
        a[i].name[strlen(a[i].name)-1] = '\0'; // last newline character is deleted
        fgets(a[i].descr, 201, infile);        
        a[i].descr[strlen(a[i].descr)-1] = '\0';
        fscanf(infile, "%d\n", &a[i].status);
    }
    fclose(infile);
 
    // printing jobs
    for (i = 0; i < N; i++) {
        if (strcmp(a[i].name, "Joe") == 0) {
            printf("\nJob #%d\n", a[i].id);
            printf("Description: %s\n", a[i].descr);
            printf("Status: %d\n", a[i].status);
        }
    }

    // setting the status
    for (i = 0; i < N; i++) {
        if (strcmp(a[i].name, "Joe") == 0)
            a[i].status = 1;
    }

    // saving it to file
    outfile = fopen("jobs2.txt", "w");
    if (outfile==NULL) {
        printf("Cannot open jobs2.txt\n");
        return 2;
    }    

    // save number of jobs first
    fprintf(outfile, "%d\n", N);
    // save all job assignments
    for (i = 0; i < N; i++)
        fprintf(outfile, "%d\n%s\n%s\n%d\n", a[i].id, a[i].name, a[i].descr, a[i].status);

    fclose(outfile);
    
    free(a);
    return 0;
}

5. Binary files

To practice binary files,

  • write a program that loads the job assignemnts from jobs.txt and saves it into jobs.bin in a binary format,
  • write an other program that loads the binary file jobs.bin and lists all the job assignments.

Try to open jobs.bin with notepad and observe the difference between the text and the binary file formats.

Solution

The program below does the conversion from text to binary:

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

typedef struct {
    int id;
    char name[101];
    char descr[201];
    int status;
} job;

int main () {
    FILE *infile, *outfile;   // file handle
    job* a;         // start address of the dynamic array    
    int N;          // number of elements in the dynamic array
    int i;          // auxiliary variable for loops
    
    // open file
    infile = fopen("jobs.txt","r");
    if (infile==NULL) {
        printf("Cannot open jobs.txt\n");
        return 1;
    }
    
    // load the content of the file into a dynamic array
    fscanf(infile, "%d", &N);   // first integer is the number of items
    a = (job*)malloc(sizeof(job)*N);
    for (i = 0; i < N; i++) {
        fscanf(infile, "%d\n", &a[i].id);
        fgets(a[i].name, 101, infile);       // we use fgets to allow space characters in the name
        a[i].name[strlen(a[i].name)-1] = '\0'; // last newline character is deleted
        fgets(a[i].descr, 201, infile);        
        a[i].descr[strlen(a[i].descr)-1] = '\0';
        fscanf(infile, "%d\n", &a[i].status);
    }
    fclose(infile);
 
    // saving it to file
    outfile = fopen("jobs.bin", "wb");  // note that the mode is now "wb"
    if (outfile == NULL) {
        printf("Cannot open jobs.bin\n");
        return 2;
    }    

    // save number of jobs first
    fwrite(&N, sizeof(int), 1, outfile);
    // save all job assignments
    fwrite(a, sizeof(job), N, outfile);

    fclose(outfile);
    
    free(a);
    return 0;
}

The next program reads the binary file back and prints its content:

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

typedef struct {
    int id;
    char name[101];
    char descr[201];
    int status;
} job;

int main () {
    FILE *infile, *outfile;   // file handle
    job* a;         // start address of the dynamic array    
    int N;          // number of elements in the dynamic array
    int i;          // auxiliary variable for loops
    
    // open file
    infile = fopen("jobs.bin","rb");    // mode "rb" means reading binary format
    if (infile == NULL) {
        printf("Cannot open jobs.bin\n");
        return 1;
    }
    
    // load the number of jobs and create array    
    fread(&N, sizeof(int), 1, infile);
    a = (job*)malloc(sizeof(job)*N);

    // load all job assignments
    fread(a, sizeof(job), N, infile);

    fclose(infile);

    // print everything
    for (i = 0; i < N; i++) {
        printf ("%d:  %s;   %s;   %d\n", a[i].id, a[i].name, a[i].descr, a[i].status);
    }

    free(a);
    return 0;
}

6. Further problems for practicing

Merge files

Write a program that merges the content of two text files into a new file.

Count words

Write a program to count the number of words in a text file.

Statistics

Write a program to find the most frequently occurring letter in the file (be case insensitive).