4

I would like to send stdout to multiple commands, however I'm not sure how do I read from standard input within process substitution?

My attempts:

$ echo foo >(cat /dev/stdin) >(cat /dev/stdin)
foo /dev/fd/63 /dev/fd/62

$ echo foo >(cat -) >(cat -)
foo /dev/fd/63 /dev/fd/62

$ echo foo >(cat <&3) >(cat <&3) 3<&0
foo /dev/fd/63 /dev/fd/62
-bash: 3: Bad file descriptor
-bash: 3: Bad file descriptor

Alternative version of the same problem:

$ cat file | tee &>/dev/null >(cmd1 /dev/stdin) >(cmd2 /dev/stdin)

What's the right way of doing this?

kenorb
  • 20,988
  • do you mean - from a terminal? – mikeserv Dec 06 '15 at 19:24
  • @mikeserv Yes, from terminal/script using bash shell. The - points to stdin. – kenorb Dec 06 '15 at 19:25
  • well... you kind of cant from a terminal - not like that. its to do with the process groups and what a controlling terminal will do with background process that try to read... there's an answer... just a minute. – mikeserv Dec 06 '15 at 19:27
  • I've seen that, but it doesn't solve the problem. The other question asking for the explanation why it doesn't work, however I'm looking for the solution which isn't provided there. Secondly it's other way round, there is <(cat), and I am looking for: >(cat -). – kenorb Dec 06 '15 at 19:37
  • it actually is - cat | { cat <(sed 's/^/from the terminal: /'); } the single pipeline will get you a single process group. – mikeserv Dec 06 '15 at 19:41
  • 2
    What's wrong with just echo foo | tee >(cat) >(cat)? – muru Dec 06 '15 at 19:46
  • i kind of glossed echo foo >(cat) because it doesn't make a lot of sense. the echo happens after the cat. see ^ muru's comment. – mikeserv Dec 06 '15 at 19:46
  • @muru It works for cat. However I wanted to specify stdin for the script which accepts file as part of -i, so normally I could use /dev/stdin. Is there any equivalent descriptor which I can use? So I could use: >(unknown_cmd1 /dev/stdin). How cat does it know its stdin? – kenorb Dec 06 '15 at 19:49
  • echo foo | tee >(foo -i <(cat))? foo being this hypothetical command? (An example: echo foo | tee >(cat -n <(cat))) – muru Dec 06 '15 at 19:50
  • @muru Yes, it sounds correct:) E.g.: >(cmd1 -i <(cat)) assuming that <(cat) would return the file descriptor. – kenorb Dec 06 '15 at 19:52
  • if you just want a file-descriptor you can just use one: echo stuff | cmd /dev/fd/0. you wont have a lot of luck editing it in-place, though, if that's what -i is for. and thats either way. or for multiple: echo stuff | { { tee /dev/fd/3 | cmd1 /dev/fd/0 >&4; } 3>&1 | cmd2 /dev/fd/0; } 4>&1 – mikeserv Dec 06 '15 at 19:53
  • I think I was confused why it was printing /dev/fd/X instead of just printing content of it. But it's actually the output of the echo. But the content from pipe isn't actually shown (because of being in the background). So actually it works as expected. E.g.: cat /etc/hosts | tee &>/dev/null >(dd if=/dev/stdin of=/dev/stdout) >(dd if=/dev/stdin of=/dev/stdout) I guess. So my example was wrong, it suppose to be: echo foo | tee >(cat /dev/stdin) >(cat /dev/stdin) which then it works. – kenorb Dec 06 '15 at 20:01
  • @Gilles The correct answer to: "I would like to send stdout to multiple commands" does not use cat: tee FILE FILE FILE where each FILE is a Process substitution that reads (probably with read) from stdin (look at my answer). Therefore I believe that this is not the same question as Process substitution and cat: can't read stdin. I respectfully ask to reconsider the "mark as duplicate". Look especially after the > >( idiom. –  Dec 06 '15 at 23:57

1 Answers1

5

This reads from stdin:

echo foo | tee >(read line </dev/stdin; echo "internal $line")

You have to keep in mind that a process substitution acts "like" a file.
It could be used where a file is expected. The command tee expects to write to a file.
In that command we are being specific about the device to read from with: /dev/stdin. In that simple example, the /dev/stdin could be removed and that will work also:

echo foo | tee >(read line; echo "internal $line")

If I am understanding your need correctly, this will work:

$ echo foo | tee >(read a </dev/stdin; echo "a is $a") \
>(read b </dev/stdin; echo "b is $b") \
>(read c </dev/stdin; echo "c is $c")
foo
a is foo
c is foo
b is foo

I omitted the PS2 prompt to reduce confusion. Note that each Process Substitution replaces the use of a file (as: tee FILE FILE ....).

The read does not have to be the first command.

$ echo foo > >(echo "hello" | read b; read a </dev/stdin; echo "a is $a") 
a is foo

Note that here the "Process Substitution" needs a redirection,
that is the reason of the two > >( idiom.

A simple echo, will only print the number of the fd used (the name of the file):

$ echo >(echo "hello")
/dev/fd/63
hello

It is similar to:

$ echo "$file"
filename

Whereas this is a very different idiom:

$ echo > "$file"