Skip to main content
4 of 12
added 25 characters in body
Stéphane Chazelas
  • 584.7k
  • 96
  • 1.1k
  • 1.7k

On Linux and with shells that implement here-documents with writable temporary files (like bash does), you can do:

{
  out=$(ls /dev/null /x 2> /dev/fd/3)
  err=$(cat<&3)
} 3<<EOF
EOF

printf '%s=<%s>\n' out "$out" err "$err"

(where ls /dev/null /x is an example command that outputs something on both stdout and stderr).

With zsh, you can also do:

(){ out=$(ls /dev/null /x 2> $1) err=$(<$1);} =(:)

In any case, you'd want to use temporary files. Any solution that would use pipes would be prone to deadlocks in case of large outputs. You could read stdout and stderr through two separate pipes and use select()/poll() and some reads in a loop to read data as it comes from the two pipes without causing lock-ups, but that would be quite involved and AFAIK, only zsh has select() support built-in.

Another approach could be to store one of the streams in temporary memory instead of a temporary file. Like:

{
  IFS= read -rd '' err
  IFS= read -rd '' out
} < <({ out=$(ls /dev/null /x); } 2>&1; printf '\0%s' "$out")

(assuming the command doesn't output any NUL)

Note that $err will include the trailing newline character.

Stéphane Chazelas
  • 584.7k
  • 96
  • 1.1k
  • 1.7k