To clarify a few more things. In:
echo foo >&3
echo is not writing
"foo\n" to its file descriptor 3.
echo is always writing to stdout, to its file descriptor 1. It's calling the
write() system call with a
1 integer as its first argument, a pointer to an area in memory that starts with
foo\n as the second argument and
4 (the length of
foo\n) as the third.
In C, you'd write it
write(1, "foo\n", 4). In the code above, the shell redirects the fd 1 to the same open file description as open on fd 3 before calling
echo (by way of the
dup2() system call). So, even though it's functionally equivalent, it's not the same as doing
write(3, "foo\n", 4). In effect, it's something like (simplified):
if (pid = fork())
execlp("echo", "foo", 0);
echo does a
write(1, "foo\n", 4)
Except that in virtually all shells,
echo is builtin, so there's no
exec. Instead, the shell does:
saved_stdout = dup(1);
dup2(saved_stdout, 1); close(saved_stdout); /* restore stdout */
builtin_echo() is a function that does the
write(1, "foo\n", 4) in the same process).
For a command that does
write(3, "foo\n", 4), you can have a look at
print -u3 foo builtin command.
Now, every process is free to use file descriptors as they please. Except that 0, 1 and 2 are by convention reserved for stdin, stdout and stderr. Other fds are generally not special, but in shells (which are just one type of application), fds 0 to
<some-value> where some value is at least 9 are reserved for usage by the user (of the shell). The shell will not mingle with them for its own internal soup. For instance my
saved_stdout = dup(1) above was an approximation. In effect, the shell will make sure
saved_stdout will be a value greater than
Now, since there's no convention attached to fds outside 0,1,2, you can't expect anything to be open on fd 3. Most likely it will be closed. Or if it's not, it's likely the caller of your script will just have forgotten to close it (or to add the
O_CLOEXC flag to it) as there would be no reason it left it open for you as nobody expects anything to be open on fd 3.
You would use the fd 3 if you knew it had been open onto something, generally by you in the same script beforehand, like in:
var=$(cmd 2>&1 >&3)
Where fd 3 had been defined as a
dup() of fd 1 before we use it for
dup() it back onto fd 1.