See, this is why I hate using bash extensions. So many weird corner cases and surprises....
As it turns out, the answer I gave yesterday was only partially correct. The "pure sh" solution is fine, but I offered this bash alternative: cmd {fd}>&1 1>&2 2>&$fd {fd}>&- This doesn't actually work as expected. It fails to close the FD in the final redirection. What does it actually do? Hell if I know. I literally can't figure it out. Consider this function: foo() { echo out; echo err >&2; } Simple enough, right? It writes "out" to stdout, and "err" to stderr. We can use this to see what's going where, while we play games with redirections. Next, we write a simple wrapper, which should in theory swap stdout and stderr: bar() { local fd foo {fd}>&1 1>&2 2>&${fd} {fd}>&- } Running this appears to gives the correct results -- outputs go to the right places -- but it leaves the temporary FD open. The final redirection to close it doesn't work. But *this* one works: bar() { local fd foo {fd}>&1 1>&2 2>&${fd} exec {fd}>&- } Why? Again, I do not know. My theories all fail to account for all of the observed results. And, of course, if we wanted to preserve the exit status of foo, the second wrapper script doesn't do that. It'll need another variable to hold that: bar() { local fd rc foo {fd}>&1 1>&2 2>&${fd} rc=$? exec {fd}>&- return "$rc" } At this point, the "pure sh" version looks a whole lot simpler, doesn't it? THIS is why I have a wiki devoted to bash and its problems.