Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The cat, the echo, and the process substitution

Basically I am trying to understand the difference between those commands:

cat <<< yolo | tee f.txt
echo yolo | tee t.txt

And those commands:

cat <<< yolo > >(tee f.txt)
echo yolo > >(tee t.txt)

The first two commands have the exact same result: "yolo" is printed and the terminal gives back the control after that, which is exactly what I would expect.

[user@localhost ~]$ cat <<< yolo | tee f.txt

yolo

[user@localhost ~]$ echo yolo | tee t.txt

yolo

But with process substitution, something strange occurs with echo.

[user@localhost ~]$ cat <<< yolo > >(tee f.txt)

yolo

[user@localhost ~]$ echo yolo > >(tee t.txt)

[user@localhost ~]$ yolo

The terminal gives back the control before the text is printed out. Why do I get the control sooner in this case?

This must have something to do with how processes are opened and how file descriptors are passed between processes, but I am kind of reaching the limits of my knowledge.

If I pipe it to anything else, everything goes back to normal, e.g. echo yolo > >(tee t.txt) | cat.

What’s even more strange is that xargs'ing into echo works well:

[user@localhost ~]$ xargs echo <<< yolo > >(tee t.txt)

yolo

But you could say that xargs is the main program here, not echo.

And if I use an input process substitution with cat I have mixed results:

cat < <(echo yolo) > >(tee t.txt)

Sometimes it gives me this:

[user@localhost ~]$ cat < <(echo yolo) > >(tee t.txt)

[user@localhost ~]$ yolo

And sometimes this:

[user@localhost ~]$ cat < <(echo yolo) > >(tee t.txt)

yolo

So I guess this may have something to do with how fast the system executes the command, which kind of makes it unpredictable.

Does that mean that the output process substitution (e.g. tee in this example) runs in the background?

like image 296
vdavid Avatar asked Sep 06 '25 02:09

vdavid


1 Answers

Ah, I think I've found it...

Process Substitution ... The process list is run asynchronously, and its input or output appears as a filename...

Once a command (or forked process completes), control is returned to the terminal and next prompt is displayed. I originally suspect echo being built-in could have played role, but it really only skewed the timing. I.e. once using > >(tee t.txt) when does tee get to print to console is not exactly deterministic.

For that matter, try this (for your third example):

$ cat <<< yolo > >(sleep 1; tee f.txt)
$ yolo

As opposed to:

$ { echo yolo; sleep 1 ;} > >(tee t.txt)
yolo
$

Difference is that >(list) process substitution is as mentioned executed asynchronously. Try the former of the two examples, even with a greater value for sleep to simulate a long running process. It'll hang around while you keep using your shell (actually even if you terminate it, in which case it gets re-parented, but you'd still see in the process list; side note: "Commands run as a result of command substitution ignore the keyboard-generated job control signals SIGTTIN, SIGTTOU, and SIGTSTP." -> even loosing terminal won't kill them, unlike asynchronous (&) execution example below).

Pipelines on the other hand:

If the pipeline is not executed asynchronously (see Lists), the shell waits for all commands in the pipeline to complete.

Shell does not resume control (next command does not execute) until all commands from the pipeline finished execution.

Try:

$ echo yolo |  (sleep 1; tee f.txt)
yolo
$

as opposed to (similar to using >(list)):

echo yolo |  ((sleep 1; tee f.txt) &)
$ yolo

(The double sub-shell is not really needed, I've just used it to suppress job control messages in the running shell).

like image 164
Ondrej K. Avatar answered Sep 07 '25 21:09

Ondrej K.