First, note that the syntax for closing is 5>&-
or 6<&-
, depending on whether the file descriptor is being read for writing or for reading. There seems to be a typo or formatting glitch in that blog post.
Here's the commented script.
exec 5>/tmp/foo # open /tmp/foo for writing, on fd 5
exec 6</tmp/bar # open /tmp/bar for reading, on fd 6
cat <&6 | # call cat, with its standard input connected to
# what is currently fd 6, i.e., /tmp/bar
while read a; do #
echo $a >&5 # write to fd 5, i.e., /tmp/foo
done #
There's no closing here. Because all the inputs and outputs are going to the same place in this simple example, the use of extra file descriptors is not necessary. You could write
cat </tmp/bar |
while read a; do
echo $a
done >/tmp/foo
Using explicit file descriptors becomes useful when you want to write to multiple files in turn. For example, consider a script that outputs data to a data output file and logging data to a log file and possibly error messages as well. That means three output channels: one for data, one for logs and one for errors. Since there are only two standard descriptors for output, a third is needed. You can call exec
to open the output files:
exec >data-file
exec 3>log-file
echo "first line of data"
echo "this is a log line" >&3
…
if something_bad_happens; then echo error message >&2; fi
exec >&- # close the data output file
echo "output file closed" >&3
The remark about efficiency comes in when you have a redirection in a loop, like this (assume the file is empty to begin with):
while …; do echo $a >>/tmp/bar; done
At each iteration, the program opens /tmp/bar
, seeks to the end of the file, appends some data and closes the file. It is more efficient to open the file once and for all:
while …; do echo $a; done >/tmp/bar
When there are multiple redirections happening at different times, calling exec
to perform redirections rather than wrapping a block in a redirection becomes useful.
exec >/tmp/bar
while …; do echo $a; done
You'll find several other examples of redirection by browsing the io-redirection
tag on this site.
It doesn't matter because both 4>&1
and 4<&1
do the same thing: dup2(1, 4)
which is the system call to duplicate a fd onto another. The duplicated fd automatically inherits the I/O direction of the original fd. (same for 4>&-
vs 4<&-
which both resolve to close(4)
, and 4>&1-
which is the dup2(1, 4)
followed by close(1)
).
However, the 4<&1
syntax is confusing unless for some reason the fd 1 was explicitly open for reading (which would be even more confusing), so in my mind should be avoided.
The duplicated fd
shares the same open file description which means they share the same offset in the file (for those file types where it makes sense) and same associated flags (I/O redirection/opening mode, O_APPEND and so on).
On Linux, there's another way to duplicate a fd
(which is not really a duplication) and create a new open file description for the same resource but with possibly different flags.
exec 3> /dev/fd/4
While on Solaris and probably most other Unices, that is more or less equivalent to dup2(4, 3)
, on Linux, that opens the same resource as that pointed to by the fd 4 from scratch.
That is an important difference, because for instance, for a regular file, the offset of fd 3 will be 0 (the beginning of the file) and the file will be truncated (which is why for instance on Linux you need to write tee -a /dev/stderr
instead of tee /dev/stderr
).
And the I/O mode can be different.
Interestingly, if fd 4 pointed to the reading end of a pipe, then fd 3 now points to the writing end (/dev/fd/3
behaves like a named pipe):
$ echo a+a | { echo a-a > /dev/fd/0; tr a b; }
b+b
b-b
$ echo a+a | { echo a-a >&0; tr a b; }
bash: echo: write error: Bad file descriptor
b+b
Best Answer
Redirections to specific file descriptors use
3< file
. Process substitution uses<( ... )
. To combine the two you need to use both:The space is important — otherwise it's interpreted as an attempted here document that is terminated with
(
, but that isn't valid and you'll get a parse error.Your
gpg
command line needs to be:This redirects the output of the first
curl
command into descriptor 3, and the second into descriptor 4, in just the same way as if you read into them from a normal file.The way that
<( ... )
works is that it runs the command with its output connected to either a FIFO or an entry under/dev/fd
, and then<( ... )
gets replaced in the command line with the path to that file as an ordinary argument, just as though it'd been written there in the first place. The file path can then be used with other constructs, like redirection.Your original command ends up running
gpg ... 3/dev/fd/63 4/dev/fd/64
, and thencurl
finds that nobody is interested in what it's writing and reports that error. I also got an error fromgpg
itself saying that those files don't exist, although it was buried in thecurl
output.