Red Hat System Administration II
Run repetitive tasks with
forloops, evaluate exit codes from commands and scripts, run tests with operators, and create conditional structures withifstatements.
System administrators often encounter repetitive tasks in their daily activities. A repetitive task example is running a command multiple times on a target, such as checking a process every minute for 10 minutes to know whether it has completed. Another example is running a command once each for multiple targets, such as backing up many databases on a system. The for loop is a Bash looping construct to use for task iterations.
In Bash, the for loop construct uses the following syntax:
forVARIABLEinLIST; doCOMMANDVARIABLEdone
The loop processes the strings that you provide in LIST and exits after processing the last string in the list. The for loop temporarily stores each list string as the value of VARIABLE, and then executes the block of commands that use the variable. The variable name is arbitrary. Typically, you reference the variable value with commands in the command block.
Provide the list of strings for the for loop from a list that the user enters directly, or that is generated from shell expansion, such as variable, brace, or file name expansion, or command substitution.
These examples demonstrate different ways to provide strings to for loops:
[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{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))"; donekernel-tools-libs-5.14.0-70.2.1.el9_0.x86_64 was installed on Thu Mar 24 10:52:40 PM EDT 2022 kernel-tools-5.14.0-70.2.1.el9_0.x86_64 was installed on Thu Mar 24 10:52:40 PM EDT 2022 kernel-core-5.14.0-70.2.1.el9_0.x86_64 was installed on Thu Mar 24 10:52:46 PM EDT 2022 kernel-modules-5.14.0-70.2.1.el9_0.x86_64 was installed on Thu Mar 24 10:52:47 PM EDT 2022 kernel-5.14.0-70.2.1.el9_0.x86_64 was installed on Thu Mar 24 10:53:04 PM EDT 2022 [user@host ~]$for EVEN in $(seq 2 2 10); do echo "$EVEN"; done2 4 6 8 10
After a script interprets and processes all of its content, the script process exits and passes back control to the parent process that called it. However, a script can be exited before it finishes, such as when the script encounters an error condition. Use the exit command to immediately leave the script, and skip processing the remainder of the script.
Use the exit command with an optional integer argument between 0 and 255, which represents an exit code. An exit code is returned to a parent process to indicate the status at exit. An exit code value of 0 represents a successful script completion with no errors. All other nonzero values indicate an error exit code. The script programmer defines these codes. Use unique values to represent the different error conditions that are encountered. Retrieve the exit code of the last completed command from the built-in $? variable, as in the following examples:
[user@host bin]$cat hello#!/usr/bin/bash echo "Hello, world" exit 0 [user@host bin]$./helloHello, world [user@host bin]$echo $?0
When a script's exit command is used without an exit code argument, the script returns the exit code of the last command that was run within the script.
To ensure that unexpected conditions do not disrupt scripts, it is recommended to verify command input such as command-line arguments, user input, command substitutions, variable expansions, and file name expansions. You can verify integrity in your scripts by using the Bash test command.
All commands produce an exit code on completion.
To see the exit status, view the $? variable immediately after executing the test command. An exit status of 0 indicates a successful exit with nothing to report. Nonzero values indicate some condition or failure. Use various operators to test whether a number is greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), or equal (eq) to another number.
Use operators to test whether a string of text is the same (= or ==) or not the same (!=) as another string of text, or whether the string has zero length (z) or has a non-zero length (n). You can also test whether a regular file (-f) or directory (-d) exists, and has some special attributes, such as if the file is a symbolic link (-L), or if the user has read permissions (-r).
Note
Shell scripting uses many other operator types. The test(1) man page lists the conditional expression operators with descriptions. The bash(1) man page also explains operator use and evaluation, but can be complex to read. Red Hat recommends learning shell scripting through quality books and courses that are dedicated to shell programming.
The following examples demonstrate the test command with Bash numeric comparison operators:
[user@host ~]$test 1 -gt 0 ; echo $?0 [user@host ~]$test 0 -gt 1 ; echo $?1
Test by using the Bash test command syntax, [ <TESTEXPRESSION> ] or the newer extended test command syntax, [[ <TESTEXPRESSION> ]], which provides features such as file name globbing and regex pattern matching. In most cases, use the [[ <TESTEXPRESSION> ]] syntax.
The following examples demonstrate the Bash test command syntax and 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 Bash 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 Bash string unary (one argument) operators:
[user@host ~]$STRING=''; [[ -z "$STRING" ]]; echo $?0 [user@host ~]$STRING='abc'; [[ -n "$STRING" ]]; echo $?0
Note
The space characters inside the brackets are mandatory, because they separate the words and elements within the test expression. The shell's command parsing routine divides the command elements into words and operators by recognizing spaces and other metacharacters, according to built-in parsing rules. For full treatment of this advanced concept, see the getopt(3) man page. 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. Programmers incorporate decision-making into shell scripts by using conditional structures. A script can execute specific routines when stated conditions are met.
The simplest conditional structure is the if/then construct, with the following syntax:
if <CONDITION>; then
<STATEMENT>
...
<STATEMENT>
fiWith this construct, if the script meets the given condition, then it executes the code in the statement block. It does not act if the given condition is not met. Common test conditions in the if/then statements include the previously discussed numeric, string, and file tests. The fi statement at the end closes the if/then construct. The following code section demonstrates 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
You can further expand the if/then construct to take different sets of actions depending on whether a condition is met. Use the if/then/else construct to accomplish this behavior, as in this example:
if <CONDITION>; then
<STATEMENT>
...
<STATEMENT>
else
<STATEMENT>
...
<STATEMENT>
fiThe following code section demonstrates 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
Expand an if/then/else construct to test more than one condition and to execute a different set of actions when it meets a specific condition. The next example shows the construct for an added condition:
if <CONDITION>; then
<STATEMENT>
...
<STATEMENT>
elif <CONDITION>; then
<STATEMENT>
...
<STATEMENT>
else
<STATEMENT>
...
<STATEMENT>
fiIn this conditional structure, Bash tests the conditions as they are ordered in the script. When a condition is true, Bash executes the actions that are associated with the condition and then skips the remainder of the conditional structure. If none of the conditions are true, then Bash executes the actions in the else clause.
The following example demonstrates an if/then/elif/then/else statement to run the mysql client if the mariadb service is active, or to run the psql client if the postgresql service is active, or to run the sqlite3 client if both the mariadb and the postgresql service are inactive:
[user@host ~]$systemctl is-active mariadb > /dev/null 2>&1[user@host ~]$MARIADB_ACTIVE=$?[user@host ~]$sudo systemctl is-active postgresql > /dev/null 2>&1[user@host ~]$POSTGRESQL_ACTIVE=$?[user@host ~]$if [[ "$MARIADB_ACTIVE" -eq 0 ]]; then \ mysql; \ elif [[ "$POSTGRESQL_ACTIVE" -eq 0 ]]; then \ psql; \ else \ sqlite3; \ fi
References
bash(1) man page