I typically use tee
to receive a piped output data, echo it to standard output, and forward it to the actual intended recipient of the piped data. But sometimes this fails, and I cannot exactly understand why.
I'll try to demonstrate with a series of examples:
$ echo testing with this string | tee
testing with this string
So, just echoing some data to tee
without arguments, is replicated/printed on the terminal/stdout. Note that this should be tee
printing the output, as the output from echo is now "piped"/redirected, and therefore not present in stdout anymore (the same that happens here:
$ echo aa | echo bb
bb
... i.e. echo aa
output got redirected to the next command, - which, being echo b
, does not care about the input, and outputs just its own output.)
$ echo testing with this string | tee | python3 -c 'a=1'
$
Now here, piping data into tee
without arguments, - and then piping, from tee
, to a program that does not provide any output to terminal/stdout - prints nothing. I would have expected tee
here to duplicate to stdout, and then forward to the next command in the pipeline, but apparently that does not happen.
$ echo testing with this string | tee /dev/stdout
testing with this string
testing with this string
Right, so if we pipe to tee with command line argument /dev/stdout
, we get the printout twice - and as concluded earlier, it must be tee
that produces both printed lines. That means, that when used without an argument, |tee
basically does not open any file for duplicating, and simply forwards what it receives on its input, to its output; but as it is the last in the pipeline, its output is stdout in that case, so we get a single printout.
Here we get double printout, because
tee
duplicated its input stream to/dev/stdout
due to the argument (which ends up as the first printout); and then- forwarded the same input to its output, which here being stdout (as
tee
is again last in the pipeline), results with the second printout.
This also would explain why the previous ...| tee | python3 -c 'a=1'
did not print anything: tee
without arguments did not open any file for duplication, and merely forwarded to next command in the toolchain - and as the next one does not print any output either, no output is generated whatsoever.
Well, if the above understanding is correct, then this:
$ echo testing with this string | tee /dev/stdout | python3 -c 'a=1'
$
... should print at least one line (from tee
copying to /dev/stdout
; the "forwarded" part will end up being "gulped" by the final command as it prints nothing), but it does not.
So, why does this happen - where am I going wrong in my understanding of what tee
does?
And how can I use tee
, to print to stdout, also when its output is forwarded to a command that doesn't print anything to stdout on its own?
CodePudding user response:
You aren't misunderstanding tee
, you're misunderstanding what stdout is. In a pipe, like echo testing | tee | python3 -c 'a=1'
, the tee
command's stdout is not the terminal, it's the pipe going to the python
command (and the echo
command's stdout is the pipe going to tee
).
So tee /dev/stdout
sends two copies of its input (on stdin) to the exact same place: its stdout, whether that's the terminal, or a pipe, or whatever.
If you want to send a copy of the input to tee
someplace other than down the pipe, you need to send it somewhere other than stdout. Where that is depends on where you actually want to send it (i.e. why you want to copy it). If you specifically want to send it to the terminal, you could do this:
echo testing | tee /dev/tty | python3 -c 'a=1'
...while if you want to send it to the outer context's stdout (which might or might not be a terminal), you can duplicate the outer context's stdin to a different file descriptor (#3 is handy for this), and then have tee
write a copy to that:
{ echo testing | tee /dev/fd/3 | python3 -c 'a=1'; } 3>&1
Yet another option is to redirect it to stderr (aka FD #2, which is also the terminal by default, but redirectable separately from stdout) with tee /dev/fd/2
.
Note that the various /dev entries I'm using here are supported by most unixish OSes, but they aren't universal. Check to see what your specific OS provides.
CodePudding user response:
I think I got it, but am not sure if it is correct: I saw this: 19.8. Forgetting That Pipelines Make Subshells - bash Cookbook [Book].
So, if pipelines make subshells, then
echo testing with this string | tee /dev/stdout | python3 -c 'a=1'
... is conceptually equal to:
echo testing with this string | (tee /dev/stdout | (python3 -c 'a=1'))
Note that the second pipe |
redirects stdout of the subshell tee
runs in, and as /dev/stdout
is just an interface to stdout, it is redirected too, so we get nothing printed.
So, while stdout (and /dev/stdout
) is local to the (sub)shell, /dev/tty
is local to the terminal - and therefore the following:
$ echo testing with this string | tee /dev/tty | python3 -c 'a=1'
testing with this string
... in fact prints a line, as expected.