Command substitution
Command substitution is the basic shell feature that allows the output of one or more commands to be executed in place and used like variable expansion as arguments to another command expansion. In other words, the outcome of the commands is placed in a short-lived anonymous variable and substituted into the surrounding command.
Syntax
There are two acceptable syntaxes or ways to perform command substitution in bash:
1) Dollar sign syntax; and
2) Backtick syntax.
At this point, both ways are presented without my opinion.
In the wild when developers are forced to write bash scripts, it is my experience that one or the other syntax is used depending on personal preference.
Dollar sign syntax
In my opinion, this syntax is easier to read, especially when nesting command substitutions, not to mention less prone to error.
Example 1: command substitution using dollar sign syntax to test lines in a file
Most Linux environments with Coreutils commands such as cat and the shuf command also come equipped with a command called wc, which allows you to count byte, words, and lines in a file. Here we will use it to simply test if a file contains more than a certain amount of lines, then do something.
echo do something
}
Notes
The expression $( seq 101 | wc -l ) evaluates to the integer 101.As a result, the test expression becomes, test ! 101 -gt 100. Furthermore, we can take out the ! pipeline operator and evaluation of the remaining test expression. That is. I hope you would agree that test 101 -gt 100 is effectively true. We are then left with ! true on the left-hand side of the list operator ||. ! true becomes false; and false || becomes true &&. In the end, we are left with echo do something.
Backtick syntax
If you like backticks more than money, great! As is the nature of coding, you are free to choose to write code any way you want unless you must conform to some strict style guidelines. I’ll just say that you may have difficulty performing nested command substitution.
Example 2: command substitution using backtick syntax to embed nested command output into the echo command
Let’s keep things simple and output a message stating your username.
Notes
If your username happens to be ‘linuxhint’, the above command evaluates to “my username is linuxhint”.
Now that you know how to use command substitution, let’s look at ways to use it.
Fun with assignments and command substitution
Often, we want to assign a variable the output of a command. This can be accomplished using command substitution.
For example, in bash pattern matching we assigned the variable subject the letters of the alphabet as follows.
Commands
echo ${subject}
Output
Convenient! Aren’t you glad to have command substitution now!
Fun with functions and command substitution
Let’s roll our own map function which counts the number of words containing the letter a.
First, we need a function that tests if some word contains the letter a. In the following snippet, we will use pattern replacement through parameter expansion and the integer attribute on the assignment.
Commands
local instr="${1}"
local -i match=$( test ! "${instr//a}" != "${instr}" || echo 1 )
echo ${match}
}
If the result of replacing a from an input string is not itself before replacement, we say that the input string contains a letter a. In this case, we echo 1. The resulting command substitution is then subject to assignment with the integer attribute. In the case of the assignment of empty value, the assigned value is taken as 0. That is, the function has_a returns 0 or 1 depending on the presence of the letter a in the input string.
Here is a quick look at our has_a function in action.
Commands
has_a sdf
has_a df
has_a f
has_a a
Output
0
0
0
1
Next, we need a function to loop through the words in a sentence while applying the has_a function that we will simply call map.
Commands
test ! ${#} -eq 1 || { true ; return ; }
local function_name="${1}"
local first=${2}
local rest=${@:3}
echo "$( ${function_name} ${first} ) $( map ${function_name} ${rest} )"
}
Here is a quick look at our map function in action.
Commands
map has_a {a..z}{a..z}
map has_a {a..b}{a..b}{a..b}
Output
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0
Now you are in the matrix!
All we need to do now is count the 1s that we will call sum.
test ! ${#} -eq 1 || { echo 0 ; return ; }
local -i first="${1}"
local rest=$( sum ${@:2} )
first+=rest
echo ${first}
}
That should do it!
Here is a quick look at our sum function in action.
Commands
sum $( map has_a {a..z}{a..z} )
sum $( map has_a {a..c}{a..c} )
Output
51
5
More fun with assignments: setup function
While you are here, let’s have some more fun with assignments exploring what I like to call setup functions, i.e. we are going create a specialized function to assign a value to a variable. As you know by now, we may need to use command substitution. Here’s how.
Commands
echo 1
}
setup-variable() {
variable=$( variable )
}
setup() {
setup-variable
}
main() {
local variable=0
setup
echo ${variable}
}
main
echo ${variable:-empty}
Output
empty
Exercises
- Rewrite the command in Example 1 without using the pipeline operator !
- Rewrite the command in Example 2 using dollar sign syntax
- Write a function to count the words without an a using sum, map, and has_a
- Write a He/she loves me not program that loop on forever
- Write a line assigning a variable the value of the second row and third column of a CSV file (see cut command)
- Write a line assigning a variable the consents of a script (Hint: use xxd)
TLDR;
Cool! You can use bash command expansion now! As you would expect, being able to expand code into commands as you see fit, give you leverage when trying to solve real-world problems with bash programming in addition to produce reusable code. Code responsibly.
Thanks,