Quick heads up: everything in Linux is a file. You can write, read, delete and wait for a file.
Everything you type in the terminal is symlinked to /dev/pts/1.
stdin goes to /dev/fd/0 and then /dev/pts/1
stdout goes to /dev/fd/1 and then /dev/pts/1
stderr goes to /dev/fd/2 and then /dev/pts/1
Funnily enough, all 3 of them eventually go to /dev/pts/1.
Why? I thought they were different? Not so much.
This is the reason you can see them on terminal!
Here’s the simplest example of duplicating stdout
tee /dev/fd/1 <<< foo # foo # foo
Tee automatically created another file descriptor for us.
Now watch this!
This is the same as the above
echo hi | tee /dev/fd/1 # hi # hi
Instead of sending it back to stdout, send it to stderror!
echo hi | tee /dev/fd/2 # hi # hi
Now watch when we send to /dev/fd/3 or /dev/fd/0
echo hi | tee /dev/fd/3 # hi
Wtf? Why didn't this print two "hi"s?
Well, because fd/3 doesn't go anywhere useful.
The terminal isn't programmed to read from /dev/fd/3, unless you instruct it.
When you pipe the command to fd 0, the command never ends because it hasn't reached the end of the input.
Watch when you pipe to /dev/0: inputting, and inputting, and inputting!
echo hi | tee /dev/fd/0 # hi # hi # hi # hi # hi # hi # hi ...
The reason these scenarios happen is because that's just the way the terminal is designed.
0 for input
1 for output
2 for error
All 3 of them go to /dev/pts/1 eventually.
If you try piping hi to /dev/fd/2 and then redirecing stderror to /dev/null it won't show you the second command like before.
# normal way to hide error mount foo bar # mount point does not exist. mount foo bar 2>/dev/null
It's just a file bro.
echo hi | tee /dev/fd/2
# hi
# hi
echo hi | tee /dev/fd/2 2>/dev/null
# hi
echo hi | tee /dev/fd/2 2>/dev/null >/dev/null
#
That’s because all 3 of the file descriptors are actually very arbitrary. You can control them like this:
# all the same echo 1 > file echo 1 1> file echo 1 1>&1 file echo 1 1>&1 1>&1 1>&1 file
You can control them very easily too
1> is the same as >/dev/pts/1
All of these are the same thing
echo hi # hi echo hi > /dev/fd/1 # hi echo hi 1> /dev/fd/1 # hi echo hi 1> /dev/pts/1 # hi
This command should make sense now:
# echo 1, but send fd/1 as fd/0 for the next command echo 1 | echo "$( < /dev/fd/0 )" # 1
Lets rewrite that
# echo 1, but send the output to fd/0, in a subshell, which reads fd/1 as commands $(echo 1 >&0) # 1
$(echo 1) # 1: command not found $(echo 1 1>&1) # 1: command not found $(echo 1 1>&0) # 1
Same thing! 1>&0 is really just >/dev/fd/0
$(echo 1 1>&/dev/fd/1) # 1: command not found $(echo 1 1>&/dev/fd/0) # 1 $(echo 1 >&/dev/fd/1) # 1: command not found $(echo 1 >&/dev/fd/0) # 1
Lastly, another way to double the input of two different commands commands
Double stdin to one stdout
printf foo; printf bar; ( printf foo; printf bar; ) >/dev/fd/1 ( printf foo; printf bar; ) 1>&1 ( printf foo; printf bar; ) 1>&/dev/fd/1 ( printf foo; printf bar; ) 1>&/dev/pts/1 ( printf foo; printf bar; ) | xargs printf # foobar
# reverse it: send to subshell, print bar, hear from print foo ( printf foo & printf bar ) | xargs printf # barfoo
Finally, we approach two commands to one output:
# before ( comm1; comm2; ) 1>&1 # or ( comm1; comm2; ) > file
Send one command to two outputs
You actually require two processes to do this.
/dev/pts/1 cannot say two things at the exact same time, it must be sequential.
Note: $REPLY is the default variable for "read" command if you don't set one.
read <<< 1 echo $REPLY 1 read < <(echo 1) echo $REPLY 1 read var1 var2 < <(echo 1 2) echo $var1 1 echo $var2 2
# consider this read < <(echo foo) echo $REPLY > dest1 echo $REPLY > dest2 # same as this read <<< $(echo foo) echo $REPLY > dest1 echo $REPLY > dest2 # and this read <<< foo ; ( echo $REPLY > dest1 ); ( echo $REPLY > dest2 ); # and logical echo foo | tee >(read; echo "internal ${REPLY}") >(read; echo "internal2 ${REPLY}") # to multiple files echo foo | tee >(read; echo $REPLY >test1) >(echo $REPLY >test2) tee >(read; echo $REPLY >test1) >(echo $REPLY >test2) <<< foo # the best tee -a test1 -a test2 <<< foo
Pipe command to two places
echo foo | tee >(read; echo "internal ${REPLY}") >(read; echo "internal2 ${REPLY}") # foo # internal foo # internal2 foo
Pipe to two variables (assign stdout to 2 variables)
# you can't read the subshell variables echo foo | tee >(read var1) >(read var2) echo $var1 # # you cant read the subshell variables... # unless... we create more /dev/fd's! exec 300<>/tmp/300 exec 301<>/tmp/301 echo foo | tee -a /dev/fd/300 -a /dev/fd/301 # Now the message sits in the pipe, waiting to be read. read < /dev/fd/300 && echo $REPLY foo read -u 301 && echo $REPLY foo
Read stdin and stdout across subshells and terminals
# T1 exec 301<>/tmp/301 while true; do read -N 20 < /dev/urandom echo ${REPLY//[^[:ascii:]]/} > /dev/fd/301 sleep 5 done # T2 tail --follow=descriptor 301 # abc # def # etc
Triple bash stdoutput
tee -a /dev/fd/1 -a /dev/fd/1 <<< foo # foo # foo # foo
BONUS! Echo command/stdout into other terminal window! Tell one pts to talk to another pts.
Open two terminals.
ls -lha /dev/pts [user@hostname ~]$ ls -lha /dev/pts total 0 drwxr-xr-x 2 root root 0 Jun 1 14:49 . drwxr-xr-x 22 root root 3.7K Jun 1 14:49 .. crw--w---- 1 user tty 136, 0 Jun 1 22:59 0 crw--w---- 1 user tty 136, 1 Jun 1 19:18 1 crw--w---- 1 user tty 136, 2 Jun 1 19:22 2 c--------- 1 root root 5, 2 Jun 1 14:49 ptmx
echo 1 > /dev/pts/1 echo 2 > /dev/pts/2
And you'll see the command in the other terminal (TTY) where PTS is pseudo terminal.