Bash/Shell Substitution Cheat Sheet (Command vs. Process Substitution vs. Redirection etc.)
The premise of Unix is that everything is a file. Such simplicity. Yet somehow when it comes time to write the script to glue everything together, the number of concepts involved explodes:
- Pipes (
cmd | …
) - Redirection (
< some.file
) - Command substitution (
$(cmd)
) - Process substitution (
<(cmd)
) - Here docs (
<<EOF … EOF
) - Here strings (
<<<foo
)
What gives?
Plus, because the syntax is so terse,1 it took me years to wrap my head around when to use all of them. Even then, I would still back myself into abominations like:
cmd1 < <(cmd2)
(Hint: would a pipe be more straightforward here?2 cmd2 | cmd1
)
I don’t remember when it dawned on me that all the different syntaxes basically boil down to making square pegs fit in round holes, and that I could approach any problem as: (starting shape, desired shape) ⇒ syntax.
If you find yourself currently confused by the various syntaxes, hopefully I can jump start a better understanding in your brain by visually mapping out the table.
Bash Substitutions
I need: | Value Parameter | File Parameter | STDIN | |
I have: | ||||
Constant |
|
|
|
|
Multi-line Constant |
|
|
|
|
Variable |
|
|
|
|
File |
|
|
|
|
Command |
|
|
|
By Value Parameter, I mean normal parameters when calling a command. For example, foo
in this grep command:
grep -r foo .
By File Parameter, I mean parameters that are specifically expected to contain a path to a file. The second positional parameter passed to grep
is a File Parameter. Although a more typical example of when you would need process substitution (<(cmd)
) is for functionality like grep
’s --file
option, which expects a file–patterns.txt
in the below example–but you may want to be the output of a command instead for whatever reason:
grep -r -f ../patterns.txt .
By STDIN, I mean the receiving command will read your data–the output of tail -f some.log
in the below example–from standard input, instead of taking the data as parameters:
tail -f some.log | grep foo
Bonus: POSIX Substitutions
If your shell script starts with:
#!/bin/sh
Use the following table instead, since it avoids Bash-isms from the above table that won’t work in dash or Busybox.
I need: | Value Parameter | File Parameter | STDIN | |
I have: | ||||
Constant |
|
Use a mktemp file
|
|
|
Multi-line Constant |
|
Use a mktemp file
|
|
|
Variable |
|
Use a mktemp file
|
|
|
File |
|
|
|
|
Command |
|
Use a mktemp file
|
|
Notes
- Not to mention that in the pre-ChatGPT days, it was impossible to Google what most of the syntax does unless you happened to know the syntax’s true name.
- There are subtle implications to using pipes, so I am not saying you should never use
< <(cmd)
, but start with the traditional form that everyone understands (pipes) and only use other forms when there is a specific need.