13

The following line is obvious:

echo "bla" | foo | bar

But does the ones below do the same?

echo "bla" | bar <(foo)
echo "bla" | bar < <(foo)

Which of the foo and bar read "bla" from stdin and why?

I mean that, of course, I can just code it and check it, but I'm not sure if it's defined behavior or I'm exploiting features I should not rely on.

etam1024
  • 231

2 Answers2

9

That's shell dependant and not documented AFAICS. In ksh and bash, in the first case, foo will share the same stdin as bar. They will fight for the output of echo.

So for instance in,

$ seq 10000 | paste - <(tr 1 X)'
1       X
2       X042
3       X043
4       X044
5       X045
[...]

You see evidence that paste reads every other block of text from seq's output while tr reads the other ones.

With zsh, it gets the outer stdin (unless it's a terminal and the shell is not interactive in which case it's redirected from /dev/null like in cmd &). ksh (where it originated), zsh and bash are the only Bourne-like shells with support for process substitution AFAIK.

In echo "bla" | bar < <(foo), note that bar's stdin will be the pipe fed by the output of foo. That's well defined behaviour. In that case, it appears that foo's stdin is the pipe fed by echo in all of ksh, zsh and bash.

If you want to have a consistent behaviour across all three shells and be future-proof since the behaviour might change as it's not documented, I'd write it:

echo bla | { bar <(foo); }

To be sure foo's stdin is also the pipe from echo (I can't see why you'd want to do that though). Or:

echo bla | bar <(foo < /dev/null)

To make sure foo does not read from the pipe from echo. Or:

{ echo bla | bar 3<&- <(foo <&3); } 3<&0

To have foo's stdin the outer stdin like in current versions of zsh.

  • "In that case, foo's stdin is the pipe fed by echo in all of ksh, zsh and bash." You've tested this by hand just now, or is it somewhere documented? – etam1024 Sep 05 '14 at 09:02
  • Does "in the first case" in the first line refer to | foo | bar or | bar <(foo)? – Volker Siegel Sep 09 '14 at 21:03
4

echo "bla" | foo | bar: The output of echo "bla" is redirected to foo, whose output is redirected to bar.

echo "bla" | bar <(foo): That one pipes the output of echo "bla" to bar. But bar is executed with an argument. The argument is the path to the file descritor, where the output of foo will be sent to. This argument behaves like a file that contains the output of foo. So, that's not the same.

echo "bla" | bar < <(foo): One may assume that the output of echo "bla" should be sent to bar and the whole statement is equivalent to the first one. But, that's not correct. The following happens: Because the input redirection < is performed after the pipe (|), it overwrites it. So echo "bla" is not piped to bar. Instead the output of foo is redirected as standard input to bar. To clear this out see the following command and output:

$ echo "bla" | cat < <(echo "foo")
foo

As you see echo "bla" is superseded by echo "foo".

Now see this command:

$ echo "bar" | cat < <(awk '{printf "%s and foo", $0}')
bar and foo

So echo "bar" is piped to awk, which reads stdin and adds the string and foo to the output. This output is piped to cat.

chaos
  • 48,171