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

unset ARGS
while IFS= read -r -d $'\0' FILE; do
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?

4 Answers 4


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"



And if I alter the process substitution part...

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



A more simple demonstration:

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



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.

  • this... is... brilliant!
    – MestreLion
    Commented yesterday

Use a coprocess. Using the coproc builtin you can start a subprocess, read its output and check its exit status:

coproc LS { ls existingdir; }
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?

If the directory does not exist, wait will exit with a non-zero status code.

It is currently necessary to copy the PID to another variable because $LS_PID will be unset before wait is called. See Bash unsets *_PID variable before I can wait on coproc for details.

  • 1
    I'm curious as to when one would use <&"$LS" vs read -u $LS? - thanks Commented Aug 11, 2018 at 22:05
  • 1
    @BrianChrisman In this instance, probably never. read -u should work just as well. The example was meant to be generic and show how the output of the coprocess could be piped into another command. Commented Aug 14, 2018 at 12:38
  • read -u works but prints an error at the end of the loop: read: : invalid file descriptor specification Commented Jun 5, 2020 at 18:50
  • 3
    This will randomly fail, because $LS (which is $LS[0]) is unset when the co-process exits (so it's not just LS_PID). Add a sleep 1 before the loop, and you will see (read: : invalid file descriptor specification). Stay away from coproc, it's a trap for the uninitiated.
    – ddekany
    Commented May 24, 2021 at 18:11
  • 1
    @Feuermurmel Surely regretting your persistence is often the case with anything done in bash script. :) But project substitution is often needed in quite basic and shell-ish use cases, like looping through find results. So switching to Python is maybe too much hassle, if the are alternatives. Like just accepting the risk of a failing find, or using shopt -s lastpipe and the intuitive pipe solution.
    – ddekany
    Commented May 26, 2021 at 11:11

Processes in process substitution are asynchronous: the shell launches them and then doesn't give any way to detect when they die. So you won't be able to obtain the exit status.

You can write the exit status to a file, but this is clumsy in general because you can't know when the file is written. Here, the file is written soon after the end of the loop, so it's reasonable to wait for it.

… < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)

Another approach is to use a named pipe and a background process (which you can wait for).

mkfifo find_pipe
find … >find_pipe &
… <find_pipe
wait $find_pid

If neither approach is suitable, I think you'll need to head for a more capable language, such as Perl, Python or Ruby.

  • Thank you for this answer. The methods you described work fine but I must admit that they are slightly more complicated than I had anticipated. In my case I settled for a loop before the one shown in the question that iterates through all arguments and prints an error if one of them isn't a file or folder. While this doesn't handle other types of errors that could occur in the substituted process it's good enough for this specific case. If I ever need a more sophisticated error handling method in situations like this I'll certainly come back to your answer. Commented May 10, 2014 at 15:20

One approach is:

token="WzNZY3CjqF3qkasn"    # some random string
while read line; do
    if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
        echo "$line"
done < <(command; echo "$token:$?")
echo "Return code: $status"

The idea is to echo the exit status along with the random token after the command has completed, and then use bash regular expressions to look for and extract the exit status. The token is used to create a unique string to look for in the output.

It's probably not the best way to do it in a general programming sense, but it might be the least painful way to handle it in bash.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .