A pipe connects two processes. File redirects connect the pipeline to
the filesystem: infile becomes cmd1's stdin; outfile becomes cmdN's
stdout. Both use the same dup2 mechanism from the previous page,
preceded by open.
open
open opens a file and returns a file descriptor. It takes a path, a
set of flags, and — when creating a file — a permission mode:
#include <fcntl.h>
int fd;
fd = open("infile.txt", O_RDONLY);
fd = open("outfile.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);If open fails, it returns -1 and sets errno. Use perror to
print the error:
fd = open("infile.txt", O_RDONLY);
if (fd < 0) {
perror("infile.txt");
exit(1);
}Flags
| Flag | Meaning |
|---|---|
O_RDONLY | Open for reading only |
O_WRONLY | Open for writing only |
O_CREAT | Create the file if it does not exist (requires mode) |
O_TRUNC | Truncate the file to zero length on open |
O_WRONLY | O_CREAT | O_TRUNC is the standard combination for an
output file: create it if missing, overwrite it if present. The mode
0644 gives the owner read-write permission and the group and others
read-only permission — the same as most files created by shell
redirects.
The mode argument is only consulted when O_CREAT is specified. When
opening a file for reading, omit it.
Redirect infile to stdin
The child process that will run cmd1 reads from stdin. To make it read
from a file instead, dup2 the file's descriptor onto STDIN_FILENO
(0) before execing:
int infd;
infd = open(infile, O_RDONLY);
if (infd < 0) {
perror(infile);
exit(1);
}
dup2(infd, STDIN_FILENO);
close(infd); /* STDIN_FILENO now refers to the file; infd is redundant */
exec_cmd(cmd1_argv);After dup2(infd, STDIN_FILENO), file descriptor 0 reads from the
file. close(infd) removes the redundant reference — the file is still
open through STDIN_FILENO.
Redirect stdout to outfile
The child that runs cmdN writes to stdout. To redirect its stdout to a file:
int outfd;
outfd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd < 0) {
perror(outfile);
exit(1);
}
dup2(outfd, STDOUT_FILENO);
close(outfd); /* STDOUT_FILENO now refers to the file; outfd is redundant */
exec_cmd(cmdN_argv);The command does not know it is writing to a file. It writes to
STDOUT_FILENO as always; the kernel routes those bytes to the file.
Error handling order
Open both files before forking. If infile cannot be opened, there is nothing to pipe — exit before creating any child processes:
infd = open(infile, O_RDONLY);
if (infd < 0) {
perror(infile);
return (1); /* or exit(1) — no children yet, nothing to wait for */
}
outfd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd < 0) {
perror(outfile);
close(infd);
return (1);
}
/* fork here */This matches what the shell does: bash -c "< missing cmd" exits
immediately with an error and does not exec cmd. Testing file access
before forking means you never have a zombie child waiting on a parent
that will never call waitpid.
The parent does not use these fds
After forking the children that will use infd and outfd, the parent
closes both:
close(infd);
close(outfd);The same close-everything-you-do-not-use rule from the pipe page
applies here. A file descriptor opened by the parent is inherited by
every child. If outfd stays open in the parent, the file stays open
even after the last child closes it — the parent's copy holds the
reference count above zero until it too closes.
A redirect test
Extend main.c to open infile and outfile from argv and run a
two-command pipeline between them:
int main(int argc, char **argv)
{
int infd;
int outfd;
char *cmd1[] = { "cat", NULL };
char *cmd2[] = { "wc", "-l", NULL };
if (argc != 3) {
tci_printf("usage: ./pipeline infile outfile\n");
return (1);
}
infd = open(argv[1], O_RDONLY);
if (infd < 0) { perror(argv[1]); return (1); }
outfd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); /* create or overwrite; 0644 = rw-r--r-- */
if (outfd < 0) {
perror(argv[2]);
close(infd); /* already open; release before returning */
return (1);
}
run_two_redirect(cmd1, cmd2, infd, outfd);
close(infd);
close(outfd);
return (0);
}Where run_two_redirect is a version of run_two from the previous
page extended to accept and apply infd and outfd in the appropriate
children.
Test:
make re
echo -e "banana\napple\ncherry" > fruits.txt
./pipeline fruits.txt counts.txt
cat counts.txtExpected: 3 — three lines in fruits.txt, counted by wc -l, written
to counts.txt.
Compare with the shell:
< fruits.txt cat | wc -l > counts.txt
cat counts.txtSame result. The next page assembles all the pieces into the full
two-command pipeline function that main.c will call.