After completing this section, you should be able to:
Iterate over lists using for loops.
Evaluate exit codes from commands and scripts.
Perform tests using operators.
Create conditional structures using if statements.
System administrators often encounter repetitive tasks in their day-to-day activities. Repetitive tasks can take the form of executing an action multiple times on a target, such as checking a process every minute for 10 minutes to see if it has completed. Task repetition can also take the form of executing an action once across multiple targets, such as backing up each database on a system. The for loop is one of the multiple shell looping constructs offered by Bash, and can be used for task iterations.
Processing Items from the Command Line
Bash's for loop construct uses the following syntax.
forVARIABLEinLIST; doCOMMANDVARIABLEdone
The loop processes the strings provided in LIST
in order one by one and exits after processing the last string in the list.
Each string in the list is temporarily stored as the value of VARIABLE, while the for loop executes the block of commands contained in its construct.
The naming of the variable is arbitrary.
Typically, the variable value is referenced by commands in the command block.
The list of strings provided to a for loop can be supplied in several ways.
It can be a list of strings entered directly by the user, or be generated from different types of shell expansion, such as variable, brace, or file-name expansion, or command substitution.
Some examples that demonstrate the different ways strings can be provided to for
loops follow.
[user@host ~]$for HOST in host1 host2 host3; do echo $HOST; donehost1 host2 host3[user@host ~]$for HOST in host{1,2,3}; do echo $HOST; donehost1 host2 host3[user@host ~]$for HOST in host{1..3}; do echo $HOST; donehost1 host2 host3[user@host ~]$for FILE in file*; do ls $FILE; donefilea fileb filec[user@host ~]$for FILE in file{a..c}; do ls $FILE; donefilea fileb filec[user@host ~]$for PACKAGE in $(rpm -qa | grep kernel); \do echo "$PACKAGE was installed on \$(date -d @$(rpm -q --qf "%{INSTALLTIME}\n" $PACKAGE))"; doneabrt-addon-kerneloops-2.1.11-12.el7.x86_64 was installed on Tue Apr 22 00:09:07 EDT 2014 kernel-3.10.0-121.el7.x86_64 was installed on Thu Apr 10 15:27:52 EDT 2014 kernel-tools-3.10.0-121.el7.x86_64 was installed on Thu Apr 10 15:28:01 EDT 2014 kernel-tools-libs-3.10.0-121.el7.x86_64 was installed on Thu Apr 10 15:26:22 EDT 2014[user@host ~]$for EVEN in $(seq 2 2 10); do echo "$EVEN"; done2 4 6 8 10
After a script has processed all of its contents, it exits to the process that called it. However, there may be times when it is desirable to exit a script before it finishes, such as when an error condition is encountered. This can be accomplished with the use of the exit command within a script. When a script encounters the exit command, it exits immediately and does not process the remainder of the script.
The exit command can be executed with an optional integer argument between 0 and 255, which represents an exit code.
An exit code is a code that is returned after a process has completed.
An exit code value of 0 represents no error.
All other nonzero values indicate an error exit code.
You can use different nonzero values to differentiate between different types of errors encountered.
This exit code is passed back to the parent process, which stores it in the ? variable and can be accessed with $? as demonstrated in the following examples.
[user@host bin]$cat hello#!/bin/bash echo "Hello, world" exit 0[user@host bin]$./helloHello, world[user@host bin]$echo $?0
If the exit command is called without an argument, then the script exits and passes the exit status of the last command executed to the parent process.
To ensure that scripts are not easily disrupted by unexpected conditions, it is good practice to not make assumptions regarding input, such as command-line arguments, user input, command substitutions, variable expansions, and file-name expansions. Integrity checking can be performed by using Bash's test command.
Like all commands, the test command produces an exit code upon completion, which is stored as the value $?.
To see the conclusion of a test, display the value of $? immediately following the execution of the test command.
Again, an exit status value of 0 indicates the test succeeded, and nonzero values indicate the test failed.
Tests are performed using a variety of operators. Operators can be used to determine if a number is greater than, greater than or equal to, less than, less than or equal to, or equal to another number. They can be used to test if a string of text is the same or not the same as another string of text. Operators can also be used to evaluate if a variable has a value or not.
Many types of operators are used in shell scripting, in addition to the comparison operators taught here. The man page for test(1) lists the important conditional expression operators with descriptions. The bash(1) man page also explains operator use and evaluation, but is a very difficult read for beginners. It is recommended that students further their advanced shell scripting needs through books and courses dedicated to shell programming.
The following examples demonstrate the use of the test command using Bash's numeric comparison operators.
[user@host ~]$test 1 -gt 0 ; echo $?0[user@host ~]$test 0 -gt 1 ; echo $?1
Tests can be performed using the Bash test command syntax, [ <TESTEXPRESSION> ].
They can also be performed using Bash's newer extended test command syntax, [[ <TESTEXPRESSION> ]], which has been available since Bash version 2.02 and provides features such as glob pattern matching and regex pattern matching.
The following examples demonstrate the use of Bash's test command syntax and Bash's numeric comparison operators.
[user@host ~]$[ 1 -eq 1 ]; echo $?0[user@host ~]$[ 1 -ne 1 ]; echo $?1[user@host ~]$[ 8 -gt 2 ]; echo $?0[user@host ~]$[ 2 -ge 2 ]; echo $?0[user@host ~]$[ 2 -lt 2 ]; echo $?1[user@host ~]$[ 1 -lt 2 ]; echo $?0
The following examples demonstrate the use of Bash's string comparison operators.
[user@host ~]$[ abc = abc ]; echo $?0[user@host ~]$[ abc == def ]; echo $?1[user@host ~]$[ abc != def ]; echo $?0
The following examples demonstrate the use of Bash's string unary operators.
[user@host ~]$STRING=''; [ -z "$STRING" ]; echo $?0[user@host ~]$STRING='abc'; [ -n "$STRING" ]; echo $?0
The space characters inside the test brackets are mandatory, because they separate the words and elements within the test expression. The shell's command parsing routine divides all command lines into words and operators by recognizing spaces and other metacharacters, using built-in parsing rules. For full treatment of this advanced concept, see the man page getopt(3). The left square bracket character ([) is itself a built-in alias for the test command. Shell words, whether they are commands, subcommands, options, arguments or other token elements, are always delimited by spaces.
Simple shell scripts represent a collection of commands that are executed from beginning to end. Conditional structures allow users to incorporate decision making into shell scripts, so that certain portions of the script are executed only when certain conditions are met.
Using the if/then Construct
The simplest of the conditional structures in Bash is the if/then construct, which has the following syntax.
if <CONDITION>; then
<STATEMENT>
...
<STATEMENT>
fi
With this construct, if a given condition is met, one or more actions are taken.
If the given condition is not met, then no action is taken.
The numeric, string, and file tests previously demonstrated are frequently utilized for testing the conditions in if/then statements.
The fi statement at the end closes the if/then construct.
The following code section demonstrates the use of an if/then construct to start the psacct service if it is not active.
[user@host ~]$systemctl is-active psacct > /dev/null 2>&1[user@host ~]$if [ $? -ne 0 ]; then > sudo systemctl start psacct > fi
Using the if/then/else Construct
The if/then construct can be further expanded so that different sets of actions can be taken depending on whether a condition is met.
This is accomplished with the if/then/else construct.
if <CONDITION>; then
<STATEMENT>
...
<STATEMENT>
else
<STATEMENT>
...
<STATEMENT>
fi
The following code section demonstrates the use of an if/then/else statement to start the psacct service if it is not active and to stop it if it is active.
[user@host ~]$systemctl is-active psacct > /dev/null 2>&1[user@host ~]$if [ $? -ne 0 ]; then > sudo systemctl start psacct > else > sudo systemctl stop psacct > fi
Using the if/then/elif/then/else Construct
Lastly, the if/then/else construct can be further expanded to test more than one condition, executing a different set of actions when a condition is met.
The construct for this is shown in the following example:
if <CONDITION>; then
<STATEMENT>
...
<STATEMENT>
elif <CONDITION>; then
<STATEMENT>
...
<STATEMENT>
else
<STATEMENT>
...
<STATEMENT>
fi
In this conditional structure, Bash tests the conditions in the order presented.
When it finds a condition that is true, Bash executes the actions associated with the condition and then skips the remainder of the conditional structure.
If none of the conditions are true, Bash executes the actions enumerated in the else clause.
The following code section demonstrates the use of an if/then/elif/then/else statement to run the mysql client if the mariadb service is active, run the psql client if the postgresql service is active, or run the sqlite3 client if both the mariadb and postgresql services are not active.
[user@host ~]$systemctl is-active mariadb > /dev/null 2>&1 MARIADB_ACTIVE=$?[user@host ~]$sudo systemctl is-active postgresql > /dev/null 2>&1 POSTGRESQL_ACTIVE=$?[user@host ~]$if [ "$MARIADB_ACTIVE" -eq 0 ]; then > mysql > elif [ "$POSTGRESQL_ACTIVE" -eq 0 ]; then > psql > else > sqlite3 > fi
bash(1) man page