Home > other >  Why would tee fail to write to stdout?
Why would tee fail to write to stdout?

Time:02-10

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.

  • Related