I want to run a command. I want to continue to pipe stdout and stderr to stdout/stderr of the console, but I also want to capture stderr in a variable.
To do this with stdout is trivial stdout=$(command | tee)
, but I can't see any way to do this for stderr.
Just swap stdout and stderr before using the same practice you already know.
stderr=$(yourcommand 3>&1 >&2 2>&3 3>&- | tee /dev/fd/2)
Breaking that down:
3>&1
makes a copy of original-stdout on file descriptor 3, which was previously empty.1>&2
makes a copy of original-stderr on file descriptor 1 (new-stdout).2>&3
makes a copy of original-stdout from file descriptor 3 (original-stdout) to file descriptor 2 (new-stderr).3>&-
closes the new file descriptor 3 we created in the first step....and then tee /dev/fd/2
makes a copy of new-stdout (original-stderr) on original-stderr so there's a copy of content on the terminal, uncaptured.
Thus, these four redirections in order have the effect of swapping stdout and stderr for the duration of the capture, and logging a copy of what was originally stdout for human consumption via stderr.