Bash – the difference between &6 and /dev/fd/6

bashfile-descriptorslinuxsocket

To read from file descriptor 6 I can use <&6 or </dev/fd/6 (aka /proc/self/fd/6). Usually both work equally well. However if that file descriptor happens to be a socket, strange things happen. For example:

$ bash -c 'ls -l /dev/fd/6;cat /dev/fd/6' 6</dev/tcp/localhost/12345
lrwx------ 1 michas michas 64 Jan 10 19:50 /dev/fd/6 -> socket:[315010]
cat: /dev/fd/6: No such device or address

Here ls shows the descriptor is indeed present. But accessing the data is not possible this way. If I use cat <&6 instead everything works well again.

What is the difference between both ways of accessing the file descriptor?

Is there a good way to access a descriptor if the number if given in a variable? (</dev/fd/$fd would work, but <&$fd does not.)

(The above situation can be observed on linux, but not on OpenBSD. – Seems like that file descriptor is a regular character device there.)

Best Answer

It's so because reading from /dev/fd/ entries which represents sockets isn't implemented on Linux. You can find quite a good writeup on reasoning here. So you can call stat on the link, and that's why you see it with ls, but access is deliberately disallowed.

Now for the second part - why does bash -c 'ls -l /dev/fd/6; cat <&6' 6</dev/tcp/localhost/12345 work? That's because socket is read from using socket/file API, not /proc filesystem. This is what I've observed happening:

  1. bash instance running in your terminal creates socket with fd 6.
  2. Child bash runs and calls dup2(6, 0), in order to attach your socket as cat's stdin.
  3. If dup2 call didn't fail, cat reads from stdin.

You can reproduce and observe it with:

netcat -lp 12345    # in another terminal session (GNU netcat)
strace -f -e trace=open,read,write,dup2 bash -c 'ls -l /dev/fd/6; cat <&6' \
 6</dev/tcp/localhost/12345

If you're wondering why does the bash child process have access to fd 6 - file descriptors survive fork, and if they aren't marked for closing on exec, they don't get closed there as well.

Related Question