Bash Error-Handling – Capture Exit Code and Handle Errors in Process Substitution

basherror handlingexitprocess-substitution

I have a script that parses file names into an array using the following method taken from a Q&A on SO:

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)

This works great and handles all types of filename variations perfectly. Sometimes, however, I will pass a non-existing file to the script, e.g:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...

Under normal circumstances I would have the script capture the exit code with something like RET=$? and use it to decide how to proceed. This does not seem to work with the process substitution above.

What's the correct procedure in cases like this? How can I capture the return code? Are there other more suitable ways to determine if something went wrong in the substituted process?

Best Answer

You can pretty easily get the return from any subshelled process by echoing its return out over its stdout. The same is true of process substitution:

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")

If I run that then the very last line - (or \0 delimited section as the case may be) is going to be find's return status. read is going to return 1 when it gets an EOF - so the only time $return is set to $FILE is for the very last bit of information read in.

I use printf to keep from adding an extra \newline - this is important because even a read performed regularly - one in which you do not delimit on \0 NULs - is going to return other than 0 in cases when the data it has just read in does not end in a \newline. So if your last line does not end with a \newline, the last value in your read in variable is going to be your return.

Running command above and then:

echo "$return"

OUTPUT

0

And if I alter the process substitution part...

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"

OUTPUT

1

A more simple demonstration:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done

OUTPUT

pipe

And in fact, so long as the return you want is the last thing you write to stdout from within the process substitution - or any subshelled process from which you read in this way - then $FILE is always going to be the return status you want when it is through. And so the || ! return=... part is not strictly necessary - it is used to demonstrate the concept only.