As far as I understood one end of a pipe has both read and write fd's and the other end also has read and write fd's. Thats why when we are writing using fd[1], we are closing the read end e.g. fd[0] of the same side of the pipe and when we are reading from the 2nd end using fd[0] we close the fd[1] of that end. Am I correct?

3 Answers 3


Yes, a pipe made with pipe() has two file descriptors. fd[0] for reading and fd[1] for writing.

No, you do not have to close either end of the pipe, it can be used for bidirectional communication.

Edit: in the comments you want to know how this relates to ls | less, so I'll explain that too:

Your shell has three open file descriptors: 0 (stdin), 1 (stdout) and 2 (stderr). When a shell executes a command it does something like this (I've simplified it a bit:

pid = fork();
if(pid == 0) {
    /* I am the child */
    execve(...); /* Whatever the user asked for */
else {
    waitpid(pid); /* Wait for child to complete */

File descriptors 0, 1 and 2 are inherited by the child, so input/output works as expected. If you do ls | less, something slightly different happens to do the redirection:

int pipe[2];
pipe(pipe, 0);

pid1 = fork();
if(pid1 == 0) {
    /* This is ls, we need to remap stdout to the pipe. We don't care about reading from the pipe */
    dup2(pipe[1], 1);
else {
    pid2 = fork();
    if(pid2 == 0) {
        /* This is less, it reads from the pipe */
        dup2(pipe[0], 0);
    else {

So the shell creates the pipe, forks, and just before executing it remaps the pipe to the stdin or stdout of the child processes, making data flow from process one to process 2. As shell pipes are not bidirectional, they only use one end of the pipe and close the other end (it actually closes the other end too, after duplicating the filedescriptor to stdin or stdout).


This is a summary of the chat conversation Dennis and I had, simplified so even other newbies can use it. When executing ls | less:

  1. The bash shell is the parent process that has fd[0], fd[1], and fd[2].
  2. pipe() is called by the parent, and it creates a pipe which is nothing but a buffer to accommodate data, and it has a file descriptor at each end, one for reading and one for writing.
  3. The child process inherits all the open fds; 0, 1, and 2, as well as the two for the pipe. Thus ls has its own copy of the buffer with fd[0] and fd[1], but both of them point to the same pipe fds from bash. Inside the first child process, ls is executed and now we need to remap the output to the write end of the pipe (fd[1]) and then to the console. So close(1) and dup(fd[1]) will do the remapping. close(1) only affects the ls child process, not the bash parent. Now the output of ls will be directed to the fd[1] write end of the pipe to the child. fd[0] is still the read end, and we don't want the data read by ls itself, so we still need to close that end with close(fd[0]). Again, this only closes the file descriptor in ls, not bash.
  4. We again fork to make the child process less, which inherits the pipe file descriptors. Input to less needs to read from the buffer, so we make use of fd[0]. Since fd[0] isn't stdin anymore, we need to remap it, so we close(0) and dup(fd[0]). Now fd[0] will point to stdin, so the input here is the output of ls. less uses fd[0] for reading and closes the write end fd[1] as less wants to prevent the data read by its write from being written out again
  • Nice summary. I cleaned up the comments on the original answer Mar 30, 2013 at 18:36
  • Shouldn’t less be forked before ls? writing to a pipe causes an error when the other end is closed.
    – qdii
    May 12, 2013 at 13:33

you need to close fd[1] in parent before calling fork() for less command otherwise less command waits infinitely for the input

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .