1

I have a script with a fairly complex command with several process substitutions and I need to get the exit codes from the commands in the process substitutions. I'm trying to do so via a named pipe but I'm running into issues since I've never used named pipes before.

For example, given this command:

somecommand <(someothercommand; echo $? > named_pipe) <(someothercommand; echo $? > named_pipe) &

what would be the correct way of waiting for the somecommand to finish and reading from the named_pipe all of the exit codes (assuming I don't know how many exit codes will actually be written to the named_pipe)? So far my attempts have resulted in blocking somecommand (since I'm assuming it's waiting for something to read from the named_pipe) or only reading one exit code.

3

That doesn't seem too simple to do, actually. It would probably be best if the shell provided support for waiting on the process substitutions, but I don't think Bash does that.

The other problem is that the named pipe can't know how many lines you're going to write there. The pipe will read an EOF when all writers are closed, but you're likely to get one for each echo writing there. Unless they hit at the exact same time, in which case you don't.

But it seems possible to arrange the process substitutions to have a writing fd open from the start, so that the EOF appears only once after they're all finished.

Something like this, with echo somecommand and true and false standing for the actual commands:

#!/bin/bash

dir=$(mktemp -d)
p="$dir/p"
mkfifo "$p"

# whole subshell sent to the background
(exec 3> "$p"; 
 # both process substitutions get a copy of fd 3
 echo somecommand \
    <(false; echo "cmd1: $?" >&3) \
    <(true;  echo "cmd2: $?" >&3) \

) &

# read the exit statuses, this will see EOF once all the three
# background processes above finish
cat "$p"
rm -rf "$dir" 2>/dev/null

Note that the order of the lines printed to the pipe depends on the timing and is essentially random.

Also, if echo somecommand is slow to run, the output from cat "$p" can appear first. You'd need to read the data from the pipe to a variable, and then wait for the background process afterwards.

There's also a possibility without backgrounding somecommand, but it needs some more gymnastics with the filehandles:

#!/bin/bash

dir=$(mktemp -d)
p="$dir/p"
mkfifo "$p"

# open an fd for read+write (doesn't block because both open)
exec 3<>"$p"

# process substs inherit the fd, closing it when they exit
echo somecommand \
    <(false; echo "cmd1: $?" >&3) \
    <(true;  echo "cmd2: $?" >&3) \

# open another reader to keep the pipe live
exec 4<"$p"

# now we can close the writing handle
exec 3>&-

# read the data off
cat <&4
exec 4<&-

rm -rf "$dir" 2>/dev/null

It may be more straightforward to just collect the exit statuses to a regular file and read it until a known amount of lines appears.

#!/bin/bash

f=$(mktemp)

# number of process substitutions
n=2
echo somecommand \
    <(false; echo "cmd1: $?" >>"$f") \
    <(true;  echo "cmd2: $?" >>"$f") \

exec 3< "$f"
# read that many lines
for ((i = 0; i < n; i++)) do
    # if the data isn't there yet, retry reading until a new line appears
    until read line <&3; do sleep 1; done
    echo "$line";
done 
exec 3<&-

rm -f "$f"

As far as I tested, all three seem to work, but working with process substitutions and pipes can be hairy enough, so I may have missed some failure mode.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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