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
You were really close:
tr "o" "a" < <(echo "Foo")
The substitution <()
makes a file descriptor and just pastes the path to the shell. For comprehension just execute:
<(echo blubb)
You will see the error:
-bash: /dev/fd/63: Permission denied
That's why it just pastes /dev/fd/63
into the shell and /dev/fd/63
is not excutable, because it's a simple pipe. In the tr
-example above, it's echo "Foo"
that writes to the pipe and via input redirection <
it's the tr
command that reads from the file descriptor.
Best Answer
3>&4-
is a ksh93 extension also supported by bash and that is short for3>&4 4>&-
, that is 3 now points to where 4 used to, and 4 is now closed, so what was pointed to by 4 has now moved to 3.Typical usage would be in cases where you've duplicated
stdin
orstdout
to save a copy of it and want to restore it, like in:Suppose you want to capture the stderr of a command (and stderr only) while leaving stdout alone in a variable.
Command substitution
var=$(cmd)
, creates a pipe. The writing end of the pipe becomescmd
's stdout (file descriptor 1) and the other end is read by the shell to fill up the variable.Now, if you want
stderr
to go to the variable, you could do:var=$(cmd 2>&1)
. Now both fd 1 (stdout) and 2 (stderr) go to the pipe (and eventually to the variable), which is only half of what we want.If we do
var=$(cmd 2>&1-)
(short forvar=$(cmd 2>&1 >&-
), now onlycmd
's stderr goes to the pipe, but fd 1 is closed. Ifcmd
tries to write any output, that would return with aEBADF
error, if it opens a file, it will get the first free fd and the open file will be assigned it tostdout
unless the command guards against that! Not what we want either.If we want the stdout of
cmd
to be left alone, that is to point to the same resource that it pointed to outside the command substitution, then we need somehow to bring that resource inside the command substitution. For that we can do a copy ofstdout
outside the command substitution to take it inside.Which is a cleaner way to write:
(which also has the benefit of restoring fd 3 instead of closing it in the end).
Then upon the
{
(or theexec 3>&1
) and up to the}
, both fd 1 and 3 point to the same resource fd 1 pointed to initially. fd 3 will also point to that resource inside the command substitution (command substitution only redirects the fd 1, stdout). So above, forcmd
, we've got for fds 1, 2, 3:If we change it to:
Then it becomes:
Now, we've got what we wanted: stderr goes to the pipe and stdout is left untouched. However, we're leaking that fd 3 to
cmd
.While commands (by convention) assume fds 0 to 2 to be open and be standard input, output and error, they don't assume anything of other fds. Most likely they will leave that fd 3 untouched. If they need another file descriptor, they'll just do an
open()/dup()/socket()...
which will return the first available file descriptor. If (like a shell script that doesexec 3>&1
) they need to use thatfd
specifically, they will first assign it to something (and in that process, the resource held by our fd 3 will be released by that process).It's good practice to close that fd 3 since
cmd
doesn't make use of it, but it's no big deal if we leave it assigned before we callcmd
. The problems may be: thatcmd
(and potentially other processes that it spawns) has one fewer fd available to it. A potentially more serious problem is if the resource that that fd points to may end up held by a process spawned by thatcmd
in background. It can be a concern if that resource is a pipe or other inter-process communication channel (like when your script is being run asscript_output=$(your-script)
), as that will mean the process reading from the other end will never see end-of-file until that background process terminates.So here, it's better to write:
Which, with
bash
can be shorten to:To sum up the reasons why it's rarely used:
>&3
instead of>&3-
or>&3 3>&-
.Proof that it's rarely used, as you found out is that it is bogus in bash. In bash
compound-command 3>&4-
orany-builtin 3>&4-
leaves fd 4 closed even aftercompound-command
orany-builtin
has returned. A patch to fix the issue is now (2013-02-19) available.