This program is a great initiation to the concept of pipes, and redirection of pipes, in Unix.
In this program, we're gonna imitate the pipe operator (|), which allows to "pipe" the output of a program to another one.
But beyond that, we have to take a file as input, and write the output of the last command, to another file.
Seems tricky, but simple when you get the moving parts
Processes in computing
As the wikipedia page (linked above) explains in detail, a process is an instance, a "happening", of your program when it executes. You can see it as an event, but formalized. In this formalized event, this process, you get the memory space where variables are stored for the duration of time the program is executing, and the connection to the system you run on, through standard streams (STDIN, STDOUT) for example. We have to understand processes, because to be able to run programs from within our program, we'll need to create a new process for it, and launch the command from there. It will be our job to direct the input and output of this "child" process to the right place.
To understand processes further, I have to describe the essential tools we have at our disposal.
Fork is going to create a child process. This is a great illustration, taken from this website, which explains the parent/child process thing better than me.

Pauses the parent process until the child process exits. Essential for you to get the output at the right place.
Important one. This will create a communication channel between the two processes. An array of two ints (fd[2] for ex) is given as argument, before the fork. After the fork, the array being duplicated, you can use fd[1] to write data to the other process, and fd[0] to read from it. Looks cool, but that's not what we'll do with it. We're going to rotate this pipe, redirect so that the standard streams of our child processes write and read from it.
This is what will allow us to rotate/redirect the pipe. Takes two file descriptors as arguments, and replaces one by another
The command we'll use to launch another program from within our program. When it successfully runs, it replaces the current process by an instance of the program you asked it to launch. The program changes, but the file descriptors and standard streams stay the same. When it exits, its STDOUT goes where you told your child process' STDOUT to go.