Search code examples
bashshellpipestdoutexit-code

Capture stdout to variable and get the exit statuses of foreground pipe


I want to execute a command (say ls) and sed its output, then save the stdout to a variable, like this,

OUT=$(ls | sed -n -e 's/regexp/replacement/p')

After this, if I try to access the $PIPESTATUS array, I get only 0 (which is same as $?). So, how can I get both $PIPESTATUS as well as capture the entire piped command's stdout?

Note:

  • If I only executed those piped commands and didn't capture the stdout (like ls | sed -n -e 's/regexp/replacement/p'), I get expected exit statuses in $PIPESTATUS (like 0 0)
  • If I only executed single command (without piping multiple commands) using Command Substitution and captured the stdout (like OUT=$(ls)), I get expected single exit status in $PIPESTATUS (which is same as $?)

P.S. I know, I could run the command 2 times (first to capture the stdout, second to access $PIPESTATUS without using Command Substitution), but is there a way to get both in single execution?


Solution

  • You can:

    1. Use a temporary file to pass PIPESTATUS.

      tmp=$(mktemp)
      out=$(pipeline; echo "${PIPESTATUS[@]}" > "$tmp")
      PIPESTATUS=($(<"$tmp"))  # Note: PIPESTATUS is overwritten each command...
      rm "$tmp"
      
    2. Use a temporary file to pass out.

      tmp=$(mktemp)
      pipeline > "$tmp"
      out=$(<"$tmp"))
      rm "$tmp"
      
    3. Interleave output with pipestatus. For example reserve the part from last newline character till the end for PIPESTATUS. To preserve original return status I think some temporary variables are needed:

      out=$(pipeline; tmp=("${PIPESTATUS[@]}") ret=$?; echo $'\n' "${tmp[@]}"; exit "$ret"))
      pipestatus=(${out##*$'\n'})
      out="${out%$'\n'*}"
      out="${out%%$'\n'}" # remove trailing newlines like command substitution does
      

      tested with:

      out=$(false | true | false | echo 123; echo $'\n' "${PIPESTATUS[@]}");
      pipestatus=(${out##*$'\n'});
      out="${out%$'\n'*}"; out="${out%%$'\n'}";
      echo out="$out" PIPESTATUS="${pipestatus[@]}"
      # out=123 PIPESTATUS=1 0 1 0
      

    Notes:

    • Upper case variables by convention should be reserved by exported variables.