Support for {var}>...
was added to ksh93
, bash
and zsh
at the same time on a suggestion of a zsh
developer. The {var}>...
operator works in zsh
, but not for compound commands.
Also note that while in:
cmd 3>&1
The fd 3 is open only for cmd
, in
cmd {var}>&1
The dynamically allocated fd (stored in $var
) remains open after cmd
returns in both zsh
and bash
. That operator is mostly designed to be used with exec
(see also sysopen
in zsh
for a more straightforward interface to the open()
system call).
So your code above is missing exec {tmp}>&-
to release that fd afterwards in bash
.
So here you could do:
if exec {tmp}>&1; then
errors=$(exec 2>&1 >&"$tmp" {tmp}>&- && ls -ld /x /bin | tr o Z)
exec {tmp}>&-
fi
Which would work in bash
, zsh
and ksh93
and not leak a fd. (the quotes around $tmp
are only needed in bash
and when its posix
option is not enabled). Note that in ksh93
or when zsh
or bash
are in POSIX mode, a failing exec
causes the shell to exit (dup()
failing here could be caused by stdout being closed or some limit on number of open files being reached or other pathological cases for which you may want to exit anyway).
But here, you don't need a dynamically allocated fd, just use fd 3 for instance which is not used in that code:
{ errors=$(exec 2>&1 >&3 3>&-; ls -ld /x /bin | tr o Z); } 3>&1
Which would work in any Bourne-like shell.
Like in the dynamic fd approach above even if it's not as obvious, if the dup2()
(in 3>&1
) fails, the assignment will not be run, so you may want to make sure errors
is initialised before (with a unset -v errors
for instance).
Note that it doesn't matter whether the fd 3 is otherwise open or in use in the rest of the script (that original fd if open will be left untouched and restored at the end), what matters is whether the code that you are embedded inside the $(...)
expects fd 3 to be open.
Only fds 0, 1 and 2 are expected to be open by applications, other fds are not. ls
and tr
don't expect anything about fd 3. Cases where you may need to use a different fd is when your code explicitly makes use of that fd and expects it to have been open beforehand like if instead of ls
, you had cat /dev/fd/3
where fd 3 is expected to have been open to some resource somewhere earlier in your script.
To answer the question on how to assign the first free fd in POSIX shells, I don't think there's a way with the POSIX shell and utilities API. It may also not make sense. The shell may do what it wants internally with any fd provided that doesn't get in the way of its own API. For instance, you may find that fd 11 is free now, but may later be used by the shell for something internal and you writing to it could affect its behaviour. Also note that in POSIX sh, you can only manipulate fds 0 to 9.
command 3>&1
, the redirection only applies tocommand
, not to the rest of the script; you do not have to find an unused descriptor, it doesn't matter if fd3
is already used in the script. The only way this could break is if a)command
expects to be passed open fds from the parent process (which is usually well documented) b)command
expects that the first new fd opened by it will be = 3. To guard against b), use13>...
or7
instead of3
. Many people are confused by this. – Oct 02 '18 at 10:21