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.

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). 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 5 '14 at 9:02
  • Does "in the first case" in the first line refer to ` | foo | bar` or ` | bar <(foo)`? – Volker Siegel Sep 9 '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.

  • And my question is: "Is the fact that awk reads "bar" a defined behavior?" – etam1024 Sep 5 '14 at 8:58
  • Yes, it is. cmd1 < <(cmd2) behaves the same as cmd2 | cmd1. – chaos Sep 5 '14 at 9:01
  • Or cmd1 | cmd2 | cmd3 is the same as cmd1 | cmd3 < <(cmd2) – chaos Sep 5 '14 at 9:03
  • 3
    If it was wikipedia I'd put "citation needed" tag on your statements :) The whole point of my question is that it works as you say, but I couldn't find any documentation about it. – etam1024 Sep 5 '14 at 9:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.