Does one end of a pipe have both read and write fd?

11,553

Solution 1

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 */
    close(pipe[0]);
    close(1);
    dup2(pipe[1], 1);
    execve(...);
}
else {
    pid2 = fork();
    if(pid2 == 0) {
        /* This is less, it reads from the pipe */
        close(pipe[1]);
        close(0);
        dup2(pipe[0], 0);
        execve(...);
    }
    else {
        waitpid(pid1);
        waitpid(pid2);
    }
}

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).

Solution 2

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
Share:
11,553

Related videos on Youtube

John
Author by

John

Updated on September 18, 2022

Comments

  • John
    John over 1 year

    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?

  • Michael Mrozek
    Michael Mrozek about 11 years
    Nice summary. I cleaned up the comments on the original answer
  • qdii
    qdii about 11 years
    Shouldn’t less be forked before ls? writing to a pipe causes an error when the other end is closed.