How to redirect stderr in a variable but keep stdout in the console

6,905

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
Share:
6,905

Related videos on Youtube

Doubidou
Author by

Doubidou

Updated on September 18, 2022

Comments

  • Doubidou
    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
      Doubidou over 5 years
      Not 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
      sebasth over 5 years
      You can use the same answer, just do not redirect stdout.
    • Stéphane Chazelas
      Stéphane Chazelas over 5 years
      No, 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
    Doubidou over 5 years
    Ah, 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
    Stéphane Chazelas over 5 years
    That 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.