How to Use Bash Subshells Inside if Statements

If you have ever used Bash subshells ($(...)), you know how flexible subshells can be. It only takes a few characters to start a subshell to process anything required, inline to another statement. The number of possible use cases is virtually unlimited.

We can also use Bash subshells inside if statements, inline with the statement. Doing so gives the user and developer much additional flexibility when it comes to writing Bash if statements.

If you are not familiar yet (or would like to learn more about) Bash if statements, please see our Bash If Statements: If Elif Else Then Fi article.

In this tutorial you will learn:

  • How to incorporate Bash subshells inside if statements
  • Advanced methods to incorporate Bash subshells inline with other commands
  • Examples demonstrating the use of Bash subshells in if statements

How to Use Bash Subshells Inside if Statements

How to Use Bash Subshells Inside if Statements

Software requirements and conventions used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Linux Distribution-independent
Software Bash command line, Linux based system
Other Any utility which is not included in the Bash shell by default can be installed using sudo apt-get install utility-name (or yum install for RedHat based systems)
Conventions # – requires linux-commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires linux-commands to be executed as a regular non-privileged user

Example 1: Starting simple

Let us look at a simple example to start. Note that these statements, whilst executed here at the command line, can also be incorporated into a Bash shell script (a plain text file, preferably with a .sh extension, and marked as executable by using the chmod +x myscript.sh command – where myscript.sh is an example filename). We also introduce an error to make things more interesting.

$ if [ "test" == "$(echo 'test')" ]; then echo 'Matches!'; else echo 'Does not match!'; fi
Matches!
$ if [ "test" == "$(echo 'incorrect')" ]; then echo 'Matches!'; else 'Does not match!'; fi
Does not match!: command not found
$ 


In the first command, we use a simple test (if [ "some_text" == "some_other_text" ]; then ...) to check equality between two strings. For the second string, we have started a Bash subshell ($(..)) to output the word test. The result is that test matches test and so the commands after the then clause will be executed, in this case echo 'Matches!' is executed and Matches! prints.

In the second command, we change the echo command to an incorrect text match by letting the subshell echo/output incorrect ($(echo 'incorrect')). We get a strange looking error back. Look closely, can you spot the error? Also compare the second command with the first.

The issue is that in our second command, the else clause (which is executed when the equality match fails, i.e. ‘what else to do when the if statement was not true) misses an echo command. Whereas it may read fluently (if … then echo … else …) the command is incorrect as it requires an additional echo. The result is that the Bash shell tries to execute Does not match! as a literal command.

Let’s fix this up!

$ if [ "test" == "$(echo 'incorrect')" ]; then echo 'Matches!'; else echo 'Does not match!'; fi
Does not match!

Much better. And we can see our subshell, it’s echo, and the full if statement executing correctly. Great, let’s dive a little deeper.

Example 2: A bit more complex if-based subshell statement

$ VAR1='abc'; if [[ "$(echo "${VAR1}")" == *"b"* ]]; then echo 'Matches!'; else echo 'Does not match!'; fi
Matches!
$ VAR1='adc'; if [[ "$(echo "${VAR1}")" == *"b"* ]]; then echo 'Matches!'; else echo 'Does not match!'; fi
Does not match!

Here we set a variable VAR to either abc or adc and next output this variable, again using a subshell, against the presence of b in the string. Note that the original asterisk (*) prefix to the "b" compare clause indicates anything before this string and the suffix asterisk (*) similarly means anything after this string. We can see how b was found in the first abc string, but not in the second command/string where adc was used as a compare string.

Note also how we used [[...]] brackets for the if statement this time. This is unrelated to the use of subshells, and it is simply a newer Bash standard of writing if statements which can be used for additional or other use cases then the traditional [...] syntax. We require it here to do the special b matching we are attempting, using the asterisk (*) prefix and suffix to the "b" compare clause.

In a if statement with single [...] brackets this would fail:

$ if [ "abc" == *"b"* ]; then echo 'Matches!'; else echo 'Does not match!'; fi
Does not match!
$ if [[ "abc" == *"b"* ]]; then echo 'Matches!'; else echo 'Does not match!'; fi
Matches!

As the if [...] syntax does not recognize the asterisk (*) prefix and suffix to the "b" compare clause, and one needs to use [[...]] brackets instead.

Another thing to note is that this time we used double quotes (") inside the subshell (instead of the single quotes like in the first example): when one starts a subshell, such use of double quotes is not only allowed, but I can highly recommend it for various uses cases. It is handy in some situations where a lot of complex parsing is going on and a mix of single and double quotes is necessary. The double quotes will not terminate the quotes started before and outside of the subshell.

Please note that with most of the previous examples, one could have simply left off the subshell and do a simple compare directly with for example the variable, i.e.:

$ VAR1='abc'; if [[ "${VAR1}" == *"b"* ]]; then echo 'Matches!'; else echo 'Does not match!'; fi
Matches!

We chose however to introduce subshells with echo (effectively a null-operation, i.e. effectively the same as just using the variable or the text in question) as it would highlight that 1) subshells work effectively, and 2) that they can be used from within if statements.

Example 3: Advanced if-based subshell statements

We do not need to restrict our subshell usage inside if statements to a single command, nor to the use of echo alone. Let’s make a small setup:

$ touch a
$ ls --color=never ./a | wc -l 
1


We created a file named a, and counted the number of lines (using wc -l, a counting tool which can count the number of lines by using the -l option). We also made sure to introduce the --color=never option to ls to avoid parsing issues when terminal color coding is used.

Next, let’s work these statements directly into if statements:

$ if [ -z "$(ls --color=never ./a | wc -l)" ]; then echo "Empty directory output!"; fi
$ if [ "$(ls --color=never ./a | wc -l)" -eq 1 ]; then echo "Exactly one file found!"; fi
Exactly one file found!
$ 

Here we use the same ls ... wc -l code twice directly from within an if statement. The first if statement, which uses -z checks if the text between quotes (the first option to the -z if-instruction) is empty. It is not as the ls command will yield some output in this case, given that we created the file a.

In the second command, we actually test if the output from our ls ... wc -l command is equal to 1 by using the -eq test option in the if statement. eq stands for equal to. Note that -eq (and it’s reverse -ne being not equal to) can only be used for numbers. For text based strings, use == (equal) and != (not equal) instead.

The output of command (Exactly one file found!) is correct, and our if statement with incorporated multi-command subshell works fine!

Also of interest to note there is that the first compare value in the second if statement (i.e. $(ls --color=never ./a | wc -l) with output 1) is numeric. So, why have we used two double quotes ("...") around the subshell statement? This has nothing to do with subshells, and all with how if works in Bash, and one may not know this trick or shorthand yet; please consider this:

$ V='1 1'
$ if [ ${V} -eq 0 ]; then echo '0'; fi
bash: [: too many arguments
$ if [ "${V}" -eq 0 ]; then echo '0'; fi
bash: [: 1 1: integer expression expected
$ V=0
$ if [ "${V}" -eq 0 ]; then echo '0'; fi
0

In other words, using double quotes is a slightly safer way of programming Bash if statements, even if the condition is a numeric based condition. It protects against more complex strings being interpreted as individual items rather then a single value, and it returns a correct error message (integer expression expected), instead of the more ambiguous bash: [: too many arguments error.

It also does not matter to Bash that you are comparing what seems to be a text string (as indicated by "...") with a numeric value; it works, provided that the number is numeric. And if it is not, it will still provide a better error message indicating that the string is not numeric, as seen. In summary, it is better to always quote your subshell, text or variable with double quotes, even when comparing numeric items. To prove this works well, consider:

$ if [ "1" -eq "1" ]; then echo 'y'; fi
y
$ if [ "1" -eq "0" ]; then echo 'y'; fi
$ 

Conclusion

In this article, we looked at incorporating Bash subshells inside if statements. We explored several examples, from easy to advanced, on how we can use Bash subshells inside if statements. We also dived a little into using double quotes when comparing, even when comparing numeric fields. Using subshells inside other commands, and in this case if statements is a powerful way to expand your Bash scripting skills. Enjoy!



Comments and Discussions
Linux Forum