BASH Programming

Bash command expansion

On the command line or within a shell scripts there are three basic ways commands interact with each other. The first and second way is through file I/O through pipes and the environment. The third way is through a parameter of a command. However, for a command to interact with another through parameters, it or it’s resulting output must be included in the parameter list. That is where command expansion or command substitution comes into play.  Here we will go over all that you need to know about command substitution to write bash scripts like a boss!

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

$( command )

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.

test ! $( seq 101 | wc -l ) -gt 100 || {
 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

`command`

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.

echo my username is `whoami`

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.

variable=$( command args... )

For example, in bash pattern matching we assigned the variable subject the letters of the alphabet as follows.

Commands

subject=$( echo {z..a} | tr -d ' '  )
echo ${subject}

Output

zyxwvutsrqponmlkjihgfedcba

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

has_a() {
  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 asdf
has_a sdf
has_a df
has_a f
has_a a

Output

1
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

map() {
 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 b c
map has_a {a..z}{a..z}
map has_a {a..b}{a..b}{a..b}

Output

1 0 0
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.

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..b}{a..b}{a..b} )
sum $( map has_a {a..z}{a..z} )
sum $( map has_a {a..c}{a..c} )

Output

7
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

variable() {
 echo 1
}
setup-variable() {
 variable=$( variable )
}
setup() {
 setup-variable
}
main() {
 local variable=0
 setup
 echo ${variable}
}
main
echo ${variable:-empty}

Output

1
empty

Exercises

  1. Rewrite the command in Example 1 without using the pipeline operator !
  2. Rewrite the command in Example 2 using dollar sign syntax
  3. Write a function to count the words without an a using sum, map, and has_a
  4. Write a He/she loves me not program that loop on forever
  5. Write a line assigning a variable the value of the second row and third column of a CSV file (see cut command)
  6. 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,

About the author

Nicholas Shellabarger

A developer and advocate of shell scripting and vim. His works include automation tools, static site generators, and web crawlers written in bash. For work he tools with cloud computing, app development, and chatbots. He codes in bash, python, or php, but is open to offers.