Last active
July 30, 2019 01:32
-
-
Save init-js/34a79a981c8bb3b5904fc771328f055c to your computer and use it in GitHub Desktop.
process substitution useful cases
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
prints: