Bash process substitution
tldp.org
tldp.org
First, it's a convenient way to turn around pipelines inside scripts in situations where you need to feed a command output or pipeline into a read loop but the loop needs to be part of the original script process so it can manipulate variables, etc. Meaningless example of optionally incrementing a counter in a loop based on the output of a pipeline of commands:
while read LINE; do
if grep -q xyz <<< "$LINE"; then
COUNTER=$(( $COUNTER + 1 ))
fi
done < <(awk '{print $4,$7}' infile.txt |cut -d, -f3)
If you did it in the "awk ...|cut ...| while read LINE..." order, COUNTER would be getting incremented in a subshell, and the value wouldn't stick around after the loop.The other most common way I use it is to diff a couple of sets of pipelines or even just files that need sorting before comparison:
diff <(sort -u file1.txt) <(sort -u file2.txt)
Saves a ton of steps. As pointed out by jkbyc, the >() construction for output process substitution becomes really powerful when combined with tee. diff <(cut -d, -f1 quarantined-files.csv |xargs -L1 basename |sort) <(ls ./quarantine/ |sort)
(Yes, the listing from "ls" comes out sorted, but it's always possible it'll sort certain characters a bit differently than "sort" does depending on locale settings, aliases, etc, etc. Piping both outputs through "sort" should ensure the same sorting collation in all cases.)It took me a while to really grok this sort of thing, but once it clicked, it's often the first thing I think of. Basically any time you might be tempted to dump something to a temporary file, you should ask yourself "can I use process substitution?"
zcat large_file.gz | tee >(grep -F pattern1 > pattern1_results.txt) >(grep -F pattern2 > pattern2_results.txt) > /dev/null
If you stick _pv_ between zcat and tee you can even get nice info about the progress.Example: how can you redirect stdout to syslog, a specified log file, and stdout? Easy, with tee, right?
``` $my_command | tee -a >(logger -t "$0[my_command]" -p "local0.INFO") "/var/log/my_logs/$(date +%s).log") ```
Okay, that's cool. But, I have to run it for every command in my script! How inconvenient. Is there a way we can make all stdout for the script do this?
Yes! Use `exec`!
``` exec &> tee -a >(logger -t "$0[my_command]" -p "local0.INFO") "/var/log/my_logs/$(date +%s).log")
$my_command ```
It's not too long and very well written so it's an enjoyable read.
You almost always learn something new which you can integrate into your workflow.
For example, `grep -f <pattern-file>` - grep works on standard input, but if you also want to provide patterns via process output, you need to use temporary files, fifos, or process substitution.
Here's an example:
find | fgrep -f <(cut -f 2 tab-delimited-text.txt) $ echo "Process substitution is fun" | tee >(rev)
Process substitution is fun
nuf si noitutitsbus ssecorP sudo echo "fs.inotify.max_user_watches=1000000" > /etc/sysctl.conf
as the redirection happens within the shell, thus without root permissions. However, the following does work: echo "fs.inotify.max_user_watches=1000000" | sudo tee /etc/sysctl.conf
as the writing is done by the elevated tee process.You could get a similar effect in other ways (e.g., tail -f, or terminal scrollback), but I find tee is convenient.
long_running_thing | tee long_running_thing_stdout
and want to see the first few lines of output before going to do something else. The pipe causes buffering on stdout, though, so I have to sit there until 4k of output has come out on stdout.As seen on https://unix.stackexchange.com/questions/25372/turn-off-buff... , either `unbuffer long_running_thing` or `stdbuf -oL long_running_thing` causes it to output linewise instead of 4k at a time.
bash$ wc <(grep script /usr/share/dict/linux.words)
fish$ wc (grep script /usr/share/dict/linux.words | psub)
The sugar in the bash form might be nice, but it isn't required. >(wc)
which is obviously only useful with a few things like tee, but still useful.dag from GitHub came up with[0] a quick way of using psub:
bind \es 'commandline -ij "(|psub)"; commandline -f backward-word backward-word'
[0]: https://github.com/fish-shell/fish-shell/issues/719#issuecom...moreutils http://joeyh.name/code/moreutils/