4

This question already has an answer here:

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 ?

marked as duplicate by user88036, muru, Thomas, RalfFriedl, Kiwy Oct 9 '18 at 14:09

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 1
    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 ? – Doubidou Oct 9 '18 at 9:01
  • 1
    You can use the same answer, just do not redirect stdout. – sebasth Oct 9 '18 at 9:14
  • 2
    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). – Stéphane Chazelas Oct 9 '18 at 9:46
5

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)).

3

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.

  • 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 :) – Doubidou Oct 9 '18 at 9:22
  • 1
    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. – Stéphane Chazelas Oct 9 '18 at 9:44
1

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
1

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 )

Not the answer you're looking for? Browse other questions tagged or ask your own question.