1

A pipe connects the stdout of one process to the stdin of another: https://superuser.com/a/277327

Here is a simple program to take input from stdin and print it:

int main( ) {
   char str[100];
   gets( str );
   puts( str );
   return 0;
}

I can use a unix pipe to pass the input from another process:

echo "hi" | ./a.out 

My question is, what is the difference between the simple code above and using the pipe() system call? Does the system call essentially do the same job without writing to the terminal? More on Pipes: https://tldp.org/LDP/lpg/node11.html

Dan
  • 2,694
  • 1
  • 6
  • 19
  • 3
    Don't use `gets()`. It's a gaping security hole. Please read [Why is the gets function so dangerous that it should not be used?](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-so-dangerous-that-it-should-not-be-used) – Rafael Sep 18 '21 at 23:23
  • The shell uses `pipe()` to implement `|`: your question makes little sense. Also: you seem to be confused by *terminal*. Terminals are not relevant here. – wildplasser Sep 19 '21 at 00:14

2 Answers2

2

The pipe() system call allows you to get file descriptors (one for reading and one for writing) for a channel (a pipe) that allows to stream bytes through multiple processes. This is an example where a parent process creates a pipe and its child writes to it so the parent can read from it:

int main() {
    int fd[2];
    pipe(fd);
    int pid = fork();
    if (pid == 0) { // Child:
        close(fd[0]); // Close reading descriptor as it's not needed
        write(fd[1], "Hello", 5);
    } else { // Parent:
        char buf[5];
        close(fd[1]); // Close writing descriptor as it's not needed
        read(fd[0], buf, 5); // Read the data sent by the child through the pipe
        write(1, buf, 5); // print the data that's been read to stdout
    }
}

When a shell encounters the pipe (|) operator, it does use the pipe() system call, but also does additional things, in order to redirect the left operand's stdout and the right operand's stdin to the pipe. Here's a simplified example of what the shell would do for the command echo "hi" | ./a.out (keep in mind that when duplicating a file descriptor it gets duplicated to the first index available in the open files structure of the process):

int main() {
    int fd[2];
    pipe(fd);
    int pid_echo = fork();
    if (pid_echo == 0) {
        // Close reading descriptor as it's not needed
        close(fd[0]);
        // Close standard output
        close(1);
        // Replace standard output with the pipe by duplicating its writing descriptor
        dup(fd[1]);
        // Execute echo;
        // now when echo prints to stdout it will actually print to the pipe
        // because now file descriptor 1 belongs to the pipe
        execlp("echo", "echo", "hi", (char*)NULL);
        exit(-1);
    }
    int pid_aout = fork();
    if (pid_aout == 0) {
        // Close standard input
        close(0);
        // Replace standard input with the pipe by duplicating its reading descriptor
        dup(fd[0]);
        // Execute a.out;
        // Now when a.out reads from stdin it will actually read from the pipe
        // because now file descriptor 0 belongs to the pipe
        execl("./a.out", "./a.out", (char*)NULL);
        exit(-1);
    }
}
altermetax
  • 696
  • 7
  • 16
  • Why does everyone talk about child/parent process with pipes? My program doesn't create a child process, it just wants to communicate with another process which comes after `|` in my command line in the terminal. Are you saying the unix pipe `|` itself is being implement using the `pipe()` system call? if I have `a | b`, the temrinal forks `a` and `b` and uses the pipe() syscall to communicate between them? – Dan Sep 18 '21 at 23:04
  • " if I have a | b, it forks a and b and uses the pipe() syscall to communicate between them?" — exactly. Your program does not need to deal with pipes, the shell does it before calling `exec` on it. While your program runs, it writes to the file descriptor indexed `1`, which is normally `stdout`, but which the shell has replaced with the pipe. Same goes for stdin. – altermetax Sep 18 '21 at 23:05
  • ah I see, but if my small little C code wants to comminuted with other processes using the `|` in the command line, all it has to do is read/write to the stdin/out? – Dan Sep 18 '21 at 23:06
  • Yes - if you're going to run your program from the command line with the above command, just read from stdin and you will actually read from the other process's stdout. – altermetax Sep 18 '21 at 23:07
  • Aren't almost all programs run from the command line? in what other situation you would want to actually use the pipe system call? if my own program has child process then it has to use it? – Dan Sep 18 '21 at 23:09
  • If you have a program `a.out` and you run it like `./a.out`, then when it reads from stdin it will actually expect input from the keyboard. However if you run it like `echo "something" | a.out` then when it reads from stdin it will read from the output of `echo` (i.e. `something\n`). – altermetax Sep 18 '21 at 23:10
  • Yes but what I'm asking is, when do you actually use the `pipe()` syscall in C if the unix `|` does everything for you? – Dan Sep 18 '21 at 23:12
  • 1
    You use it when you need to communicate with another process but you also need to write to the screen or read from the keyboard. See the first example in my answer: there a pipe is used without replacing `stdin` or `stdout` with it. Also, `pipe()` is used by the shell to implement `|`, as you can see in the second example. – altermetax Sep 18 '21 at 23:13
  • Does there have to be a fork()? can't I just use the pipes instead of `printf` to commutate with another process, without forking? – Dan Sep 18 '21 at 23:17
  • You can't, for the simple reason that you would have no way to pass the file descriptor for that pipe to the other process. However you can if, instead of a regular pipe, you use a named pipe, which the other process can open as if it were a file (see https://linux.die.net/man/3/mkfifo). – altermetax Sep 18 '21 at 23:19
  • If what you're asking is how you can replace another (already running) process's `stdin` with a pipe, that just can't be done. You only have control of processes which you create yourself via `fork`. – altermetax Sep 18 '21 at 23:21
  • So say my `a.out` forks to a child process (which I don't need at all, the child process just sits there I guess). All `a` wants to do is read inputs from `echo`, is this doable, fork is needed here? I guess im confused with how pipes work. – Dan Sep 18 '21 at 23:23
  • hmm, i think the only way to pass data from `echo` is to use the unix `|` command, and the terminal is forking and one process becomes the child of the other. I think I get it. `pipe()` syscall is no good in my example for what I want to do, a pipe only works between parent child process. – Dan Sep 18 '21 at 23:25
  • 1
    If you're running your program as `echo "hello" | ./a.out` then you don't need to do anything. Just read from stdin and you're good. However, if you're running it as `./a.out` and you want `a.out` to automatically spawn an `echo` whose output it then reads, you need to `fork` and run `execlp("echo", "echo", "hello", (char*)NULL);` on the child process, after replacing its `stdout` with the pipe (via `close` and `dup`). Instead, if you have an already running `echo` and you want your process to read from its `stdout`, you just can't. – altermetax Sep 18 '21 at 23:26
  • got it, thank you. so to confirm, a pipe only works between parent child processes? – Dan Sep 18 '21 at 23:28
  • 1
    Between any process that is either: -the creator of the pipe; -any descendant of the creator of the pipe – altermetax Sep 18 '21 at 23:29
  • Can you explain why we use `exit(-1);` in the forked programms? Wouldn't it mean that they have ended with errors? – Ilya Loskutov Jan 26 '23 at 16:06
  • @IlyaLoskutov Yes, that's because they *are* ending with errors. When you run `execl` you replace the executable your process is running with another one. That means that the current source code stops getting executed in that process (i.e. everything happening after the `exec` call gets ignored). The *only case when it is not ignored* is when `exec` fails. – altermetax Jan 27 '23 at 19:04
1

A pipe is an inter-process communication mechanism that leverages I/O redirection. However, pipes are not involved in all I/O redirection.

Since child processes may inherit file descriptors from their parent process, a parent process may change what files the child's standard streams point to, unbeknownst to the child process. This is I/O redirection.

Rafael
  • 7,605
  • 13
  • 31
  • 46
  • Let's assume I'm not using fork() in any way, all I want to do is take input from pipe and write output back to it, is stdin/out all I need at that point (like my example eabve)? – Dan Sep 18 '21 at 22:49
  • If you're asking if a single process could write to and read from a pipe it set up with itself -- totally impractical, but yes. – Rafael Sep 18 '21 at 22:55
  • I don't understand. `./a.out ` reads its input from the output of another process `echo`, isn't that what pipes are? what do you mean by `a pipe it set up with itself`? – Dan Sep 18 '21 at 22:58
  • Again, I/O redirection -- pipe or no pipe -- is practically unbeknownst to the child process. The child may read from stdin or write to stdout and not know what files each standard stream refers to. – Rafael Sep 18 '21 at 23:04