Skip to content

[中文版本](/posts/c教程/zh-CN/07-贴近系统/064-Pipes and Redirection)

Now that you can read and write with file descriptors, you can connect two processes so that one’s output becomes the other’s input, just likels | grep c in a shell.

This magic happens through pipes, one of Unix’s simplest and most elegant inter-process communication (IPC) mechanisms.

Step 1. What Is a Pipe? ​

A pipe is a unidirectional data channel between two file descriptors, one for reading, one for writing.

In the shell:

ls | grep main

is equivalent to:

  • Process A (ls) writes into the pipe.
  • Process B (grep) reads from the pipe.

In C, you can do the same thing usingpipe() andfork().

Step 2. Creating a Pipe ​

#include <unistd.h>
#include <stdio.h>
int main(void) {
    int fds[2];
    if (pipe(fds) == -1) {
        perror("pipe failed");
        return 1;
    }
    printf("Read end: %d, Write end: %d\n", fds[0], fds[1]);
    return 0;
}

Compile and run:

gcc pipe_demo.c -o pipe_demo
./pipe_demo

Output example:

Read end: 3, Write end: 4

You now have two connected file descriptors:

  • fds[0]: read end
  • fds[1]: write end

Whatever you write intofds[1] can be read fromfds[0].

Step 3. Writing and Reading Through a Pipe ​

#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(void) {
    int fds[2];
    pipe(fds);
    const char msg[] = "hello through pipe";
    write(fds[1], msg, strlen(msg));
    char buf[64];
    ssize_t n = read(fds[0], buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("Received: %s\n", buf);
    close(fds[0]);
    close(fds[1]);
}

Output:

Received: hello through pipe

You’ve just communicated through memory between two file descriptors, no files, no network.

Step 4. Pipes Between Parent and Child ​

Here’s where it gets powerful: pipes can connect processes.

#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void) {
    int fds[2];
    pipe(fds);
    pid_t pid = fork();
    if (pid == 0) {
        // Child
        close(fds[1]); // close write end
        char buf[64];
        ssize_t n = read(fds[0], buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("Child got: %s\n", buf);
    } else {
        // Parent
        close(fds[0]); // close read end
        const char msg[] = "Hi from parent!";
        write(fds[1], msg, strlen(msg));
        close(fds[1]);
    }
}

Output:

Child got: Hi from parent!

Parent writes, child reads, a clean data channel between two processes.

Step 5. Redirecting STDIN/STDOUT to a Pipe ​

You can usedup2() to connect a pipe directly to standard input/output.

Example: connect parent’s write end to child’s stdin.

#include <unistd.h>
#include <stdio.h>
int main(void) {
    int fds[2];
    pipe(fds);
    pid_t pid = fork();
    if (pid == 0) {
        dup2(fds[0], STDIN_FILENO);
        close(fds[1]);
        execlp("wc", "wc", "-w", NULL);
    } else {
        close(fds[0]);
        write(fds[1], "Hello from parent\nThis is a pipe test\n", 39);
        close(fds[1]);
    }
}

Output:

6

Here’s what happens:

  • Parent writes data into the pipe.
  • Child’s stdin is connected to that pipe.
  • wc -w counts the words received.

This is exactly how the shell implements pipelines likeecho "hi" | wc -w.

Step 6. Chaining Multiple Commands ​

You can chain multiple commands by creating multiple pipes and connecting them in series.

Example concept:

cat file.txt | grep "error" | wc -l

Each process reads from the previous pipe and writes to the next, the shell’s fundamental design.

You can implement the same concept in C by:

  1. Creating a pipe between each process pair.
  2. Forking a new process for each command.
  3. Redirecting its stdin/stdout viadup2().

Step 7. Named Pipes (FIFOs) ​

Pipes normally exist only between related processes. To share data between unrelated programs, you can use named pipes (FIFOs).

Create one:

mkfifo mypipe

Then in one terminal:

cat > mypipe

And in another:

cat < mypipe

Data flows through the named pipe like a file.

In C:

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
int main(void) {
    mkfifo("mypipe", 0666);
    int fd = open("mypipe", O_WRONLY);
    write(fd, "Hello FIFO\n", 11);
    close(fd);
}

Step 8. Error Handling and EOF ​

If all write ends of a pipe are closed,read() returns0, indicating EOF.

int fds[2];
pipe(fds);
close(fds[1]); // no writers
char buf[10];
ssize_t n = read(fds[0], buf, 10); // n == 0 => EOF

If you try to write after all readers are gone, you’ll getSIGPIPE.

Tiny Code: Minimal Shell Pipeline ​

#include <unistd.h>
#include <sys/wait.h>
int main(void) {
    int fds[2];
    pipe(fds);
    if (fork() == 0) {
        dup2(fds[1], STDOUT_FILENO);
        close(fds[0]);
        execlp("ls", "ls", NULL);
    }
    if (fork() == 0) {
        dup2(fds[0], STDIN_FILENO);
        close(fds[1]);
        execlp("wc", "wc", "-l", NULL);
    }
    close(fds[0]);
    close(fds[1]);
    wait(NULL);
    wait(NULL);
}

Run:

gcc pipe_chain.c -o pipe_chain
./pipe_chain

Output:

(number of files in directory)

You just recreatedls | wc -l in C.

Step 9. Combining Redirection and Files ​

You can redirect stdout or stderr to files the same way:

int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, STDOUT_FILENO);
close(fd);
execlp("ls", "ls", NULL);

Now the output ofls goes tooutput.txt.

Step 10. Why It Matters ​

Pipes and redirection are the heart of Unix philosophy:

  • Programs do one thing well.
  • Communicate through text streams.
  • Compose complex workflows by chaining simple tools.

Understanding how to implement pipes makes you capable of:

  • Building your own shell
  • Connecting processes dynamically
  • Implementing inter-process communication safely

Try It Yourself ​

  1. Recreatels | grep c using twofork() calls and one pipe.
  2. Buildcat file | wc -l.
  3. Implement a “tee” program that duplicates output to both stdout and a file.
  4. Create a named pipe and write to it from one process, read from another.
  5. Extend your shell to support pipelines of any length.

Next, you’ll explore how processes signal and interrupt each other, using signals and signal handlers, a crucial concept for handling interrupts, timeouts, and graceful termination.

本站总访问 — 次