Bash command substitution with redirection: bad file descriptor

bashcommand-substitutiondashio-redirection

the following command works in dash but fails in bash with "Bad file descriptor".

$ dash -c 'out=$(echo "to fd3" >&3; echo "to stdout") 3>&1; echo "out: $out"'
to fd3
out: to stdout

$ bash -c 'out=$(echo "to fd3" >&3; echo "to stdout") 3>&1; echo "out: $out"'
bash: 3: Bad file descriptor
out: to stdout

when i replace the command substition with a subshell then it seems to work in dash and bash.

$ dash -c '(echo "to fd3" >&3; echo "to stdout") 3>&1'
to fd3
to stdout

$ bash -c '(echo "to fd3" >&3; echo "to stdout") 3>&1'
to fd3
to stdout

versions:

$ bash --version
GNU bash, version 4.4.12(1)-release (x86_64-unknown-linux-gnu)

don't know how to get dash version. the man page on my system is dated January 19, 2003.


research:

i looked up how bash and dash executes a command. this is what i found.

for bash: https://www.gnu.org/software/bash/manual/bashref.html#Shell-Operation

for dash: http://man7.org/linux/man-pages/man1/dash.1.html (section "Simple Commands")

as far as i understand both do expansions before redirections. command substitution is an expansion. so it makes sense that file descriptor 3 is not set up in the command substitution.

why does it work in dash? why does it not work in bash? is it a bug in dash? or bash? is it a valid construct at all?

Best Answer

Whether redirections are performed before or after assignment expansion is unspecified by POSIX when there's no command, so both are valid and you can't rely on either. So portably, you'd need:

{ out=$(echo "to fd3" >&3; echo "to stdout"); } 3>&1

AT&T ksh and the Bourne shell behave like bash; zsh, pdksh, yash behave like dash in this instance.