Skip to content

Instantly share code, notes, and snippets.

@init-js
Last active July 30, 2019 01:32
Show Gist options
  • Save init-js/34a79a981c8bb3b5904fc771328f055c to your computer and use it in GitHub Desktop.
Save init-js/34a79a981c8bb3b5904fc771328f055c to your computer and use it in GitHub Desktop.
process substitution useful cases
#!/usr/bin/env bash
# from bash manual:
# Process substitution is supported on systems that
# support named pipes (FIFOs) or the /dev/fd method of nam‐ ing open
# files. It takes the form of <(list) or >(list). The process list is
# run with its input or output connected to a FIFO or some file in
# /dev/fd. The name of this file is passed as an argument to the
# current command as the result of the expansion. If the >(list) form
# is used, writing to the file will provide input for list. If the
# <(list) form is used, the file passed as an argument should be read to
# obtain the output of list.
export maxnum=-1
# reads numbers on stdin. updates global variable maxnum with the largest number
# read so far.
update_max () {
local num
while read num; do
if [[ "$num" -gt "$maxnum" ]]; then
maxnum="$num"
echo "new max: $maxnum" >&2
fi
done
}
# prints a static set of numbers, one per line
print_numbers () {
echo "2
4
1
10
-5
100
"
}
# in pipelines A | B | C:
# Each command in a pipeline is executed as a separate process (i.e., in a subshell).
#
print_numbers | update_max
echo "$maxnum" # prints -1 (not 100)
# because update_max runs in a child process, variable updates in the subprocess
# are not visible in parent shell.
# If we use process subsitution instead, then the first command
# runs in the foreground -- so the variable changes are
# visible.
maxnum=-1 # reset the experiment
update_max < <(print_numbers)
echo "$maxnum" # prints 100 -- which is what we would expect
# another place where I've seen command subsitution turn useful is when you have
# a program that needs a filename, and not from stdin. E.g:
wc -l <(print_numbers)
# <(print_numbers) will be substituted with "/dev/fd/N" in the arguments to wc.
# /dev/fd/N will be a fifo connected to the output of the pipeline inside the <().
# lastly, process substitution can be used to fork multiple pipelines
# out of the same command.
# e.g. generate a list of numbers. send the even numbers down one pipeline, and send
# the odd ones into a different pipeline.
for i in 1 3 5 7 9 10 11; do
# even numbers on fd4, odd on fd5
if [[ $((i % 2)) == 0 ]]; then
echo "$i" >&4
else
echo "$i" >&5
fi
done \
4> >(: even pipeline here;
num=$(wc -l);
sleep 1;
echo "read $num even number(s)") \
5> >(: odd number pipeline here;
num=$(wc -l);
sleep 1;
echo "read $num odd one(s)")
# prints "read 6 odd one(s)". and "read 1 even number(s)"
# (the two pipelines run in parallel, so the order of the 2 lines are printed is uncertain)
echo "main script done"
# Note: the main bash process won't wait for programs started
# as part of command substitutions. For instance,
# "main script done" will (likely) print, and the main shell
# will exit whether or not the pipelines above are done.
#
# You'd have to implement a synchronization
# mechanism to have the pipelines communicate completion to
# the main process. Gets hairy quickly when fifos are involved.
@init-js
Copy link
Author

init-js commented Jul 30, 2019

prints:

BASH > ./process_sub.sh
new max: 2
new max: 4
new max: 10
new max: 100
-1
new max: 2
new max: 4
new max: 10
new max: 100
100
7 /dev/fd/63
main script done
BASH > read 6 odd one(s)
read 1 even number(s)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment