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?
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With