Get exit code from subshell through the pipes
Asked Answered
L

4

17

How can I get exit code of wget from the subshell process?

So, main problem is that $? is equal 0. Where can $?=8 be founded?

$> OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt" ); echo "$?"
0

It works without tee, actually.

$> OUT=$( wget -q "http://budueba.com/net" ); echo "$?"
8

But ${PIPESTATUS} array (I'm not sure it's related to that case) also does not contain that value.

$> OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt" ); echo "${PIPESTATUS[1]}"    

$> OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt" ); echo "${PIPESTATUS[0]}"
0

$> OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt" ); echo "${PIPESTATUS[-1]}"
0

So, my question is - how can I get wget's exit code through tee and subshell?

If it could be helpful, my bash version is 4.2.20.

Levirate answered 14/2, 2012 at 13:36 Comment(0)
C
24

By using $() you are (effectively) creating a subshell. Thus the PIPESTATUS instance you need to look at is only available inside your subshell (i.e. inside the $()), since environment variables do not propagate from child to parent processes.

You could do something like this:

  OUT=$( wget -q "http://budueba.com/net" | tee -a "file.txt"; exit ${PIPESTATUS[0]} );
  echo $? # prints exit code of wget.

You can achieve a similar behavior by using the following:

  OUT=$(wget -q "http://budueba.com/net")
  rc=$? # save exit code for later
  echo "$OUT" | tee -a "file.txt"
Checani answered 14/2, 2012 at 13:42 Comment(3)
But is it possible to export all ${PIPESTATUS} array up to parent shell?Hoggard
That is not how export works - it exports variables as environment variables for child processes (from the parent).Checani
Here is a solution for bringing the entire ${PIPESTATUS[@]} array into the parent script. It involves adding it to the end of the created variable and then pulling it off into a new array.Technetium
B
8

Beware of this when using local variables:

local OUT=$(command; exit 1)
echo $? # 0

OUT=$(command; exit 1)
echo $? # 1
Bluepoint answered 17/3, 2017 at 9:49 Comment(2)
Declaring local OUT then assigning OUT= on a new line correctly sets the exit code variable $? if you don't want scope creep.Longo
ShellCheck points out this exact issue, so simply heed the warning from ShellCheck and you should be good to go ...Maccarthy
C
0

For everyone using bash. I think that by far cleanest solution to both get output value and each exit status in pipe is this.

  1. A one-time preparation. Enable lastpipe shell option. It allows to get value from the last command in pipe without using subshell. If you're in an interactive shell, also disable job control: set +m (The latter isn't needed for scripts - job control is disabled there by default.)

    shopt -s lastpipe
    set +m
    
  2. read the value into your variable, and use PIPESTATUS naturally. E.g.

    grep -E "\S" "file.txt" | sort | uniq | read -d '' RES
    # 'read' exit status 1 means all input was read till EOF, we're OK with that
    if (( PIPESTATUS[0] > 1 || PIPESTATUS[1] > 0 || PIPESTATUS[2] > 0 || PIPESTATUS[3] > 1 )); then
      echo "ERROR"
    else
      echo "$RES"
    fi
    

The above reads all non-empty lines from "file.txt", sorts them and removes duplicates. In this example we have custom processing of exit codes: grep exit status 1 means no lines were selected, we're OK with that; read exit status 1 is expected for saving multi-line output (at least, I don't know another clean way to do it). Clean means without cluttering the code just to avoid the exit status of 1 of read.

NOTE: the read -d '' option is used to read all the input into our variable (by disabling delimiter read stops at, which by default is a newline symbol). This is how we save multi-line output. If your pipe has just one line then you can use plain read RES (and likely can change expected exit status from read to 0 and not 1 as above).

Crinkumcrankum answered 3/3, 2023 at 11:1 Comment(0)
H
-1

Copy the PIPESTATUS array first. Any reads destroy the current state.

declare -a PSA  
cmd1 | cmd2 | cmd3  
PSA=( "${PIPESTATUS[@]}" )

I used fifos to solve the sub-shell/PIPESTATUS problem. See bash pipestatus in backticked command?
I also found these useful: bash script: how to save return value of first command in a pipeline?
and https://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another/70675#70675

Hanhana answered 2/11, 2013 at 4:3 Comment(1)
The code you provided doesn't answer the question. If the multiple links have the answer about getting the result from sub shell + getting the exit codes then please provide an excerpt right in the answer.Crinkumcrankum

© 2022 - 2024 — McMap. All rights reserved.