How to redirect stderr in a variable but keep stdout in the console
Solution 1
Just use:
{ err=$(cmd 2>&1 >&3 3>&-); } 3>&1
To get the stderr of cmd
while leaving its stdout untouched (here by using fd 3 to bring the original stdout (copied with 3>&1
) inside the command substitution (restored with >&3
after we've redirected fd 2 to the pipe created by the command substitution with 2>&1
)).
Solution 2
You can use the answer provided by Stéphane Chazelas with minor modifications: only redirect stderr and do not use command substitution to run your command.
{
command 2> /dev/fd/3
res=$?
err=$(cat<&3)
} 3<<EOF
EOF
printf 'stderr: %s\n' "$err"
Output of command
will be normally in stdout and stderr is in err
variable.
Solution 3
Combining answers from both How to output to screen overriding redirection and How to capture stderr of a bash keyword (e.g. time)?, I get
var=$( (cmd >/dev/tty) 2>&1)
If you're not using a builtin or keyword, that simplifies to
var=$( cmd 2>&1 >/dev/tty )
Solution 4
Just exchange stdout and stderr for the command to capture stderr.
pull=$(sudo ./pull "${TAG}" 3>&2 2>&1 1>&3)
And then redirect stderr back to stdout:
{ pull=$(sudo ./pull "${TAG}" 3>&2 2>&1 1>&3; } 2>&1
Explanation:
In the same way as when you capture only stdout of a command:
var=$(cmd)
the output of stderr still goes to the command line. You can swap both channels to capture only the stderr:
var=$(cmd 3>&2 2>&1 1>&3)
And then, redirect the stderr back to stdout (to show the output (if any)).
{ var=$(f 3>&2 2>&1 1>&3); } 2>&1
Related videos on Youtube
Doubidou
Updated on September 18, 2022Comments
-
Doubidou over 1 year
My goal is to call a command, get stderr in a variable, but keep stdout (and only stdout) on the screen. Yep, that's the opposite of what most people do :)
For the moment, the best I have is :
#!/bin/bash pull=$(sudo ./pull "${TAG}" 2>&1) pull_code=$? if [[ ! "${pull_code}" -eq 0 ]]; then error "[${pull_code}] ${pull}" exit "${E_PULL_FAILED}" fi echo "${pull}"
But this can only show the stdout in case of success, and after the command finish. I want to have stdout on live, is this possible ?
EDIT
Thanks to @sebasth, and with the help of Redirect STDERR and STDOUT to different variables without temporary files, I write this :
#!/bin/bash { sudo ./pull "${TAG}" 2> /dev/fd/3 pull_code=$? if [[ ! "${pull_code}" -eq 0 ]]; then echo "[${pull_code}] $(cat<&3)" exit "${E_PULL_FAILED}" fi } 3<<EOF EOF
I admit this not really "beautiful", seems tricky, and I don't really understand why the heredoc is needed...
Is this the better way to achieve that ?
-
Doubidou over 5 yearsNot really, if I redirect stdout in A and stderr in B, I can independently print A or B but when the command is already finished. I want A printed as the command processes ; did I miss something ?
-
sebasth over 5 yearsYou can use the same answer, just do not redirect stdout.
-
Stéphane Chazelas over 5 yearsNo, not duplicate of Redirect STDERR and STDOUT to different variables without temporary files. It's much simpler here. (there probably is a different duplicate somewhere though).
-
-
Doubidou over 5 yearsAh, you wrote this while I was editing my questions ^^ - mark a accepted answer, but I'm waiting to see if there is a less complicated way :)
-
Stéphane Chazelas over 5 yearsThat convoluted stuff in the other answer was because we needed a temp file there. We don't need one here as we don't need stdout and stderr in different variables. As noted there, it's shell and OS dependent.