While you can enter individual shell commands at the command line, there are times when doing so is not efficient. For example, if you need to enter the same 15 commands to accomplish a certain task, a single shell script can handle the job faster than you can type in those commands. However, shell scripts go beyond just repeating commands. They allow you to modify their behavior through the use of variables and change the course of their actions based on results of conducted tests.
In addition, you can configure shell scripts to run at night unattended, while you're asleep. Even better is that you can write a single set of shell scripts and then deploy them to all the various servers you administer. If written well, these scripts will need little to no modifications for the different systems on which they reside. Due to their effectiveness, flexibility, and efficiency, creating well-written shell scripts is a core function of properly administering a system.
When you are first learning, writing shell scripts is often an intimidating task. It's helpful to try some basic scripts in order to get comfortable with the process, before you launch into more complicated and intricate script writing. The basic scripts and methods in this section will help you build a solid base for creating shell scripts.
To place shell commands in a text file, first you'll need to use a text editor (covered in Chapter 8, “Working with Text Files”) to create a file. It is considered good form to use the .sh
file extension in your script file's name. While it's not required, it is helpful to be able to quickly glance at a directory and see which files are scripts by their filename extension.
When creating a shell script file, you should specify the shell you are using in the first line of the file. The format (also called syntax) for this is as follows:
#!/bin/bash
This first line of a shell script file, which is a pound sign (#
) followed by the exclamation point () and an absolute directory reference to a shell (
/bin/bash
), tells the current shell which shell to use for running the script.
After indicating the shell, commands are entered onto each line of the file. The shell will process commands in the order in which they appear in the file. An example shell script file looks like this:
$ cat sampleScript1.sh
#!/bin/bash
# This sample script dislays the date/time
# and who is logged onto the system
#
date
who
$
Notice that three additional lines were included that start with the pound symbol and add a description of the script. Lines that start with the pound symbol (other than the first #!
line) aren't interpreted by the shell. This is a great way to leave comments for yourself about what the script does and what's happening in the script, so when you come back to it two years later, you can easily remember what you did.
There are several different methods for running a shell script. We'll just cover three basic ones here.
bash
command. This method is the easiest and is often used by those who are just learning how to write and run shell scripts. Your current working directory needs to be the same directory where the script is located. You type in bash
and follow with the script's name:
$ bash sampleScript1.sh
./
. This method requires a little more work. Before attempting to run the script for the first time, you must ensure that the script file has the execution privilege bit set prior to running it. Here is an example of this:
$ chmod u+x sampleScript1.sh
$ ./sampleScript1.sh
chmod u+x
was used. Keep in mind that you only need to set the execution privilege one time. Once set, you can just enter ./
and follow it with your script's name, but your current working directory needs to be the same directory where the script is located.PATH
environment variable (covered in Chapter 6, “Working with the Shell”), or you have modified the variable to include the script's directory. The convenience of this method is that you can be located anywhere on the Linux system to run the script, and nothing other than the script name will launch it:
$ sampleScript1.sh
PATH
directory. Also, you'll need to give the script a unique name so that its name isn't the same as another script on the system or shell command.When you run a script, you'll see output from the script's commands. Here's an example of using bash
to run a sample script:
$ pwd
/home/sysadmin/Scripts
$
$ ls sampleScript1.sh
sampleScript1.sh
$
$ bash sampleScript1.sh
Fri Mar 5 19:19:11 UTC 2021
sysadmin tty1 2021-03-05 18:45
sysadmin pts/0 2021-03-05 18:48 (192.168.0.103)
$
Success! Now all of the pieces are in the right places to execute the new shell script file.
Most shell commands produce their own output, which is displayed on the terminal where the script is running. Many times, however, you will want to add your own text messages to help the script user know what is happening within the script. You can do this with the echo
command. The echo
command can display a simple text string if you add the string following the command:
$ echo This is a test
This is a test
$
Notice that, by default, you don't need to use quotes to delineate the string you're displaying. However, sometimes this can get tricky if you are using quotes within your string:
$ echo Let's see if this'll work
Lets see if thisll work
$
The echo
command uses either double or single quotes to delineate text strings. If you use them within your string, you need to use one type of quote within the text and the other type to delineate the string:
$ echo "This is a test to see if you're paying attention"
This is a test to see if you're paying attention
$
$ echo 'Rich says "scripting is easy".'
Rich says "scripting is easy".
$
Now all of the needed quotation marks appear properly in the output.
You can add echo
statements anywhere in your shell scripts where you need to display additional information:
$ cat sampleScript2.sh
#!/bin/bash
# This sample script dislays the date/time
# and who is logged onto the system
#
echo "The date and time are: "
date
echo "The following users are logged on: "
who
$
When you run this script, it produces the following output:
$ chmod u+x sampleScript2.sh
$ ./sampleScript2.sh
The date and time are:
Fri Mar 5 19:41:16 UTC 2021
The following users are logged on:
sysadmin tty1 2021-03-05 18:45
sysadmin pts/0 2021-03-05 18:48 (192.168.0.103)
$
That's nice, but what if you want to echo
a text string on the same line as a command output? You can use the -n
parameter for the echo
statement to do that. Just change the first echo
statement line to this:
echo -n "The time and date are: "
You'll need to use quotes around the string to ensure that there's a space at the end of the echoed string. The command output begins exactly where the string output stops. The output will now look like this:
$ ./sampleScript2.sh
The date and time are: Fri Mar 5 19:42:50 UTC 2021
The following users are logged on:
sysadmin tty1 2021-03-05 18:45
sysadmin pts/0 2021-03-05 18:48 (192.168.0.103)
$
Perfect! The echo
command is a crucial piece of shell scripts that interact with users. You'll find yourself using it in many situations, especially when you want to display the values of script variables. Let's look at that next.
Just running individual commands from the shell script is useful, but this has its limitations. Often you'll want to incorporate other data in your shell commands to process information. You can do this by using variables. Variables allow you to temporarily store information within the shell script for use with other commands in the script. This section shows how to use variables in your shell scripts.
You've already seen one type of Linux variable in action. (Chapter 6 described the environment variables available in the Linux system.) You can access these values from your shell scripts as well.
The shell maintains environment variables that track specific system information, such as the name of the system, the name of the user logged in to the system, the user's system ID (called UID), the default home directory of the user, and the search path used by the shell to find programs.
You can tap into these environment variables from within your scripts by using the environment variable's name preceded by a dollar sign. This is demonstrated in the following script:
$ cat sampleScript3.sh
#!/bin/bash
# Display a user's information
#
echo "User info for userid: $USER"
echo UID: $UID
echo HOME: $HOME
$
The $USER
, $UID
, and $HOME
environment variables are used to display the pertinent information about the logged-in user. The output should look something like this:
$ chmod u+x sampleScript3.sh
$ ./sampleScript3.sh
User info for userid: sysadmin
UID: 1000
HOME: /home/sysadmin
$
Notice that the environment variables in the echo
commands are replaced by their current values when the script is run. Also notice that we were able to place the $USER
system variable within the double quotation marks in the first string, and the shell script was still able to figure out what we meant.
In addition to the environment variables, a shell script allows you to set and use your own variables within the script. Setting variables allows you to temporarily store data and use it throughout the script.
User variables can be any text string of up to 20 letters, digits, or an underscore character. User variables are case sensitive, so the variable Var1
is different from the variable var1
. This little rule often gets novice script writers in trouble.
Values are assigned to user variables using an equal sign. No spaces can appear between the variable, the equal sign, and the value (another trouble spot for novices). Here are a few examples of assigning values to user variables:
var1=10
var2=-57
var3=testing
var4="still more testing"
The shell script stores all variable values as text strings, so it's up to the individual commands in the shell to properly handle the data type used for the variable's value.
Just like system variables, user variables are referenced using the dollar sign:
$ cat sampleScript4.sh
#!/bin/bash
# Trying out user variables
#
days=10
guest="London Radford"
echo "$guest checked in $days days ago."
#
days=5
guest="Takoda Puddle"
echo "$guest checked in $days days ago."
$
Running the script produces the following output:
$ chmod u+x sampleScript4.sh
$ ./sampleScript4.sh
London Radford checked in 10 days ago.
Takoda Puddle checked in 5 days ago.
$
Each time the variable is referenced, it produces the value currently assigned to it. Variables defined within the shell script maintain their values throughout the life of the shell script but are deleted when the shell script completes.
One of the most useful features of shell scripts is the ability to extract information from the output of a command and assign it to a variable. Once you assign the output to a variable, you can use that value anywhere in your script. This comes in handy when processing data in your scripts.
There are two ways to assign the output of a command to a variable:
$()
formatBe careful with the backtick character, which is not the normal single quotation mark character you are used to using for strings. Because it is not used often outside of shell scripts, you may not even know where to find it on your keyboard. On a U.S. keyboard, it is usually on the same key as the tilde symbol (~).
Command substitution allows you to assign the output of a shell command to a variable. While this doesn't seem like much, it is a major building block in script programming.
You must surround the entire command-line command with either the backtick characters:
testDate=`date`
or using the $()
format:
testDate="$(date)"
The shell runs the command within the command substitution characters and assigns the output to the variable testDate
. Notice that there aren't any spaces between the assignment equal sign and the command substitution character. Also notice that we enclosed the $(date)
command in quotation marks. This helps to avoid unusual problems that can occur with a command's output and is considered good form.
Here's an example of creating a variable using the output from a normal shell command:
$ cat sampleScript5.sh
#!/bin/bash
# Trying out command subsitution variables
#
testDate="$(date)"
echo "The date and time are: $testDate"
$
The variable testDate
receives the output from the date
command, and it is used in the echo
statement to display it. Notice that this user variable name, testDate
, starts with a lowercase letter and has an uppercase letter in its middle. This naming convention is called camel case and allows easier reading of variable names created out of multiple words by shell script writers. It's often used when naming shell scripts too.
Running the shell script produces the following output:
$ chmod u+x sampleScript5.sh
$ ./sampleScript5.sh
The date and time are: Fri Mar 5 20:23:02 UTC 2021
$
That's not all that exciting in this example (you could just as easily put the command in the echo
statement), but once you capture the command output in a variable, you can do anything with it.
When a shell script ends, it returns an exit status back to the parent shell that launched it. The exit status tells us if the shell script completed successfully.
Linux provides us with the special $?
variable, which holds the exit status value from the last command that executed. To check the exit status of a command, you must view the $?
variable immediately after the command ends. It changes values according to the exit status of the last command executed by the shell:
$ ls *.sh
sampleScript1.sh sampleScript3.sh sampleScript5.sh
sampleScript2.sh sampleScript4.sh
$ echo $?
0
$
$ ls *.txt
ls: cannot access '*.txt': No such file or directory
$ echo $?
2
$
By convention, the exit status of a command that successfully completes is 0. If a command completes with an error, a positive integer value appears as the exit status.
You can change the exit status of your shell scripts by using the exit
command and using a number as an argument. While you don't have to include an exit status with your script's exit
command, it is helpful in certain situations, such as debugging a script problem. To use an exit status, just specify the status value you want in the exit
command:
$ cat sampleScript6.sh
#!/bin/bash
# Trying out an exit status
#
testDate="$(date)"
echo "The date and time are: $testDate"
exit 42
$
$ chmod u+x sampleScript6.sh
$ ./sampleScript6.sh
The date and time are: Mon Mar 8 15:45:17 UTC 2021
$ echo $?
42
$
In this example script, an exit
command was used to exit the script using an exit status code of 42
. After the script was run, we displayed the $?
variable value to see if it matched what we had set in the exit
command, which it did.
As you write more complicated scripts, you can indicate errors by changing the exit status value. That way, by checking the exit status, you can easily debug your shell scripts. If you don't want to include an exit status in your script, it is still considered good form to use the exit
command—just don't add an exit status to it.
The most basic method of passing data to your shell script is to use command-line parameters. These parameters allow you to add data values to the command line when you execute the script:
$ ./sampleScript7.sh 3 7
[…]
$
This example passes two command-line parameters (3
and 7
) to the script sampleScript7.sh
. The script handles the command-line parameters using special variables. This section describes how to use command-line parameters in your Bash shell scripts.
The Bash shell assigns special variables, called positional parameters, to all of the command-line parameters entered. This includes the name of the script the shell is executing. The positional parameter variables are standard numbers, with $0
being the script's name, $1
being the first parameter, $2
being the second parameter, and so on, up to $9
for the ninth parameter.
Here's a simple example of using two command-line parameters in a shell script:
$ cat sampleScript7.sh
#!/bin/bash
# Trying out using command-line parameters
#
total=$[ $1 + $2 ]
echo "When you add $1 to $2,"
echo "the result is: $total"
exit
$
$ chmod u+x sampleScript7.sh
$ ./sampleScript7.sh 3 7
When you add 3 to 7,
the result is: 10
$
You can use the $1
and $2
variables just like any other variables in the shell script. The shell script automatically assigns the value from the command-line parameter to the variable, so you don't need to do anything special.
In the preceding example, the command-line parameters used were both numerical values. You can also use text strings as parameters:
$ cat sampleScript8.sh
#!/bin/bash
# Trying out using a string parameter
#
echo Hello $1, glad to meet you.
exit
$
$ chmod u+x sampleScript8.sh
$ ./sampleScript8.sh Kathi
Hello Kathi, glad to meet you.
$
The shell passes the string value entered into the command line to the script. However, you'll have a problem if you try to do this with a text string that contains spaces:
$ ./sampleScript8.sh Kathi Duggan
Hello Kathi, glad to meet you.
$
Remember that each of the parameters is separated by a space, so the shell interpreted the space as just separating the two values. To include a space as a parameter value, you must use quotation marks (either single or double quotation marks), which are not included as part of the data, but just delineate the beginning and the end of the data:
$ ./sampleScript8.sh 'Kathi Duggan'
Hello Kathi Duggan, glad to meet you.
$
$ ./sampleScript8.sh "Kathi Duggan"
Hello Kathi Duggan, glad to meet you.
$
If your script needs more than nine command-line parameters, you can continue, but the variable names change slightly. After the ninth variable, you must use braces around the variable number, such as ${10}
. Here's an example of doing that:
$ cat sampleScript9.sh
#!/bin/bash
# Trying out using 12 parameters
#
totalA=$[ $1 + $2 + $3 + $4 ]
totalB=$[ $5 + $6 + $7 + $8 +$9 ]
totalC=$[ ${10} + ${11} + ${12} ]
#
total=$[ $totalA + $totalB + $totalC ]
#
echo
echo "When you add all these numbers together,"
echo "the result is: $total"
echo
echo "When you add ${10}, ${11} and ${12} together,"
echo "the result is: $totalC"
#
exit
$
$ chmod u+x sampleScript9.sh
$ ./sampleScript9.sh 1 2 3 4 5 6 7 8 9 10 11 12
When you add all these numbers together,
the result is: 78
When you add 10, 11 and 12 together,
the result is: 33
$
This technique allows you to add as many command-line parameters to your scripts as you could possibly need.
So far, all the shell scripts presented process commands in a linear fashion—one command after another. However, not all programming is linear. There are times when you'd like your program to test for certain conditions, such as if a file exists or if a mathematical expression is 0, and perform different commands based on the results of the test. For that, the Bash shell provides logic statements.
Logic statements allow us to test for a specific condition or conditions and then branch to different sections of code based on whether the condition evaluates to a zero (true) or nonzero (false) exit value. There are a couple of different ways to implement logic statements in Bash scripts.
The most basic logic statement is the if-then condition statement. The format for the if-then
condition statement is as follows:
if [ condition ]
then
commands
fi
If the condition
you specify evaluates to a zero exit value (true), the shell runs the commands in the then
section of code. If the condition
evaluates to a nonzero exit value (false), the shell script skips the commands in the then
section of code.
The if-then condition
syntax is typically tricky for those who are new to shell script writing. It is critical to ensure a space is included after the first bracket and before the condition
, as well as after the condition
and before the last bracket: [
condition
]
.
The condition expression has quite a few different formats in the Bash shell programming. There are built-in tests for numerical values, string values, and even files and directories. Table 19.1 lists the different built-in tests that are available.
TABLE 19.1 Condition Tests
TEST | TYPE | DESCRIPTION |
---|---|---|
n1 -eq n2
|
Numeric | Checks if n1 is equal to n2
|
n1 -ge n2
|
Numeric | Checks if n1 is greater than or equal to n2
|
n1 -gt n2
|
Numeric | Checks if n1 is greater than n2
|
n1 -le n2
|
Numeric | Checks if n1 is less than or equal to n2
|
n1 -lt n2
|
Numeric | Checks if n1 is less than n2
|
n1 -ne n2
|
Numeric | Checks if n1 is not equal to n2
|
str1 = str2
|
String | Checks if str1 is the same as str2
|
str1 = str2
|
String | Checks if str1 is not the same as str2
|
str1 < str2
|
String | Checks if str1 is less than str2
|
str1 > str2
|
String | Checks if str1 is greater than str2
|
-n str1
|
String | Checks if str1 has a length greater than zero |
-z str1
|
String | Checks if str1 has a length of zero |
-d file
|
File | Checks if file exists and is a directory |
-e file
|
File | Checks if file exists |
-f file
|
File | Checks if file exists and is a file |
-r file
|
File | Checks if file exists and is readable |
-s file
|
File | Checks if file exists and is not empty |
-w file
|
File | Checks if file exists and is writable |
-x file
|
File | Checks if file exists and is executable |
-O file
|
File | Checks if file exists and is owned by the current user |
-G file
|
File | Checks if file exists and the default group is the same as the current user |
file1 -nt file2
|
File | Checks if file1 is newer than file2
|
file1 -ot file2
|
File | Checks if file1 is older than file2
|
Here is an example of using several if-then condition
statements in a shell script:
$ cat sampleScript10.sh
#!/bin/bash
# Trying out if-then conditions
#
if [ -z $1 ]
then
echo "You did not provide any parameters"
exit
fi
#
if [ -z $2 ]
then
echo "You did not provide a second parameter"
exit
fi
#
if [ $1 -eq $2 ]
then
echo "Both values are equal"
exit
fi
#
if [ $1 -gt $2 ]
then
echo "$1 is greater than $2"
exit
fi
#
if [ $1 -lt $2 ]
then
echo "$1 is less than $2"
fi
#
exit
$
We'll try not providing enough parameters first and see how the if-then
statement tests string length using the -z
condition test. This is an excellent condition test to include, if command-line parameters are used in a shell script.
$ chmod u+x sampleScript10.sh
$ ./sampleScript10.sh
You did not provide any parameters
$
$ ./sampleScript10.sh 73
You did not provide a second parameter
$
Now we can try providing enough parameters and have the script determine information concerning them:
$ ./sampleScript10.sh 73 42
73 is greater than 42
$
$ ./sampleScript10.sh 42 73
42 is less than 73
$
$ ./sampleScript10.sh 42 42
Both values are equal
$
Whenever the command from an if-then
statement in the script evaluated to a zero exit value, the statements between then
and fi
were processed by the shell script.
You can reduce the number of if-then
statements through the use of Boolean logic to combine tests. These operators are available:
[
conditionA
] && [
conditionB
]
[
conditionA
] || [
conditionB
]
The first operator is an “and” test, where both conditions must return a zero exit status (true). The second operator performs an “or” test. In this case, only one condition must return a zero exit status.
Modifying our if-then
sample script in this manner allows us to consolidate the first two tests, as shown snipped here:
$ cat sampleScript11.sh
#!/bin/bash
# Trying out if-then conditions
# and compound tests
#
if [ -z $1 ] || [ -z $2 ]
then
echo "You didn't provide the correct number of parameters."
exit
fi
#
if [ $1 -eq $2 ]
[…]
exit
$
The script still works as needed, but with a less code:
$ chmod u+x sampleScript11.sh
$
$ ./sampleScript11.sh
You didn't provide the correct number of parameters.
$
$ ./sampleScript11.sh 42
You didn't provide the correct number of parameters.
$
$ ./sampleScript11.sh 42 73
42 is less than 73
$
In the if-then
statement, you have only one option for whether a command is successful. If the command returns a nonzero exit value (false), the Bash shell just moves on to the next command in the script. In this situation, it would be nice to be able to execute an alternate set of commands. That's exactly what the if-then-else
statement is for.
The if-then-else
statement provides another group of commands in the statement:
if [ condition ]
then
commands
else
commands
fi
When the command in the if
statement line returns with a zero exit value, the commands listed in the then
section are executed, just as in a normal if-then
statement. When the command in the if
statement line returns a nonzero exit value, the Bash shell executes the commands in the else
section.
Modifying our if-then
sample script to include an if-then-else
statement allows us to provide more information concerning the parameters, as shown snipped here:
$ cat sampleScript12.sh
#!/bin/bash
# Trying out if-then-else statments
[…]
#
if [ $1 -eq $2 ]
then
echo "Both values are equal"
exit
else
echo "The values are not equal"
fi
#
if [ $1 -gt $2 ]
then
echo "$1 is greater than $2"
exit
fi
#
if [ $1 -lt $2 ]
then
echo "$1 is less than $2"
fi
#
exit
$
$ chmod u+x sampleScript12.sh
$
$ ./sampleScript12.sh 42 42
Both values are equal
$
$ ./sampleScript12.sh 42 73
The values are not equal
42 is less than 73
$
The script still works as needed but allows additional information to be provided through the else
section of the if-then-else
statement. However, there are additional methods to provide more information and consolidate code. We'll explore one technique next.
Often, you'll find yourself trying to evaluate the value of a variable, looking for a specific value within a set of possible values, similar to what we demonstrated in earlier scripts. Instead of having to write multiple if-then
statements testing for all of the possible conditions, you can use a case
statement.
The case
statement allows you to check multiple values of a single variable in a list-oriented format:
case variable in
pattern1) commands1;;
pattern2 | pattern3) commands2;;
*) default commands;;
esac
The case
statement compares the variable specified against the different patterns. If the variable matches the pattern, the shell executes the command(s) specified for the pattern. You can list more than one pattern on a line, using the bar operator to separate each pattern as an “or” operator. The asterisk (*
) symbol is the catchall for values that don't match any of the listed patterns.
Here is an example of using the case
statement within a script:
$ cat sampleScript13.sh
#!/bin/bash
# Trying out case statements
#
case $PWD in
/home/sysadmin)
echo "You are in your home directory"
;;
/home/sysadmin/Scripts)
echo "You are in your Scripts directory"
;;
*)
echo "You are not in your home directory, and"
echo "you are not in your Scripts directory"
;;
esac
#
exit
$
In this script, the variable being matched against the patterns is an environment variable ($PWD
) that contains the user's current working directory. Notice that you do not have to put the ;;
syntax on the same line as your last command within a selected pattern. This flexibility of the case
statement allows you to create easy-to-read shell code.
Here's this script in action:
$ echo $PWD
/home/sysadmin/Scripts
$
$ chmod u+x sampleScript13.sh
$
$ ./sampleScript13.sh
You are in your Scripts directory
$
$ cd
$ echo $PWD
/home/sysadmin
$
$ ./Scripts/sampleScript13.sh
You are in your home directory
$
The case
statement provides a much cleaner way of specifying the various options for each possible variable value.
Iterating through a series of commands is a common programming practice. Often, you need to repeat a set of commands until a specific condition has been met, such as processing all the files in a directory, all the users on a system, or all the lines in a text file.
The Bash shell provides three structures for controlling loops: for
, while
, and until
. They each have value for iterating through commands, and we cover them next.
The Bash shell provides the for
command to allow you to create a loop that iterates through a series of values. Each iteration performs a predefined set of commands using one of the values in the series. Here's the basic format of the Bash shell for
command:
for var in list
do
commands
done
You supply the series of values used in the iterations in the list
parameter. The values can be specified in several ways.
In each iteration, the variable var
contains the current value in the list. The first iteration uses the first item in the list, the second iteration uses the second item, and so on, until all the items in the list have been used.
The commands entered between the do
and done
statements can be one or more standard Bash shell commands. Within the commands, the $var
variable contains the current list item value for the iteration.
The most basic use of the for
command is to iterate through a list of values defined within the for
command itself:
$ cat sampleScript14.sh
#!/bin/bash
# Trying out the for command
#
for wordVar in Alpha Bravo Charlie Delta
do
echo The current word is $wordVar
done
#
exit
$
$ chmod u+x sampleScript14.sh
$
$ ./sampleScript14.sh
The current word is Alpha
The current word is Bravo
The current word is Charlie
The current word is Delta
$
Each time the for
command iterates through the list of values provided, it assigns the $wordVar
variable the next value in the list. The $wordVar
variable can be used just like any other script variable within the for
command statements. After the last iteration, $wordVar
remains valid throughout the remainder of the shell script. It retains the last iteration value (unless you change its value).
For large series of numbers in your for
loops, it's convenient to use the seq
command, instead of typing in all the numbers. You just enter seq
followed by the starting number and then the last number in the series, but you'll need to employ command substitution to make it work correctly:
$ cat sampleScript15.sh
#!/bin/bash
# Trying out the for with seq
#
for numVar in $(seq 1 10)
do
echo The current number is $numVar
done
#
exit
$
$ chmod u+x sampleScript15.sh
$
$ ./sampleScript15.sh
The current number is 1
The current number is 2
The current number is 3
The current number is 4
The current number is 5
The current number is 6
The current number is 7
The current number is 8
The current number is 9
The current number is 10
$
Using the $(seq 1 10)
syntax within the for
commands list allows the Bash shell to handle generating all the numbers in the list automatically. Notice that in this case of command substitution, we did not enclose the $(seq 1 10)
command in quotation marks. This is because the list would be treated as a single item, which is not what we want.
Command substitution in the list
lets you get rather fancy with your for
loops:
$ cat sampleScript16.sh
#!/bin/bash
# Getting fancy with the for loop
#
for fileVar in $(ls *.sh)
do
echo The current file is $fileVar
done
#
exit
$
$ chmod u+x sampleScript16.sh
$
$ ./sampleScript16.sh
The current file is sampleScript1.sh
The current file is sampleScript10.sh
The current file is sampleScript11.sh
The current file is sampleScript12.sh
The current file is sampleScript13.sh
The current file is sampleScript14.sh
The current file is sampleScript15.sh
The current file is sampleScript16.sh
The current file is sampleScript2.sh
The current file is sampleScript3.sh
The current file is sampleScript4.sh
The current file is sampleScript5.sh
The current file is sampleScript6.sh
The current file is sampleScript7.sh
The current file is sampleScript8.sh
The current file is sampleScript9.sh
$
In this example, all the script files in the current directory are listed using the $(ls *.sh)
command. This list of files is then processed through the command that's between do
and done
. You can use multiple commands here too, if needed.
The while
command is somewhat of a cross between the if-then
statement and the for
loop. The while
command allows you to define a command to test and then loop through a set of commands for as long as the defined test command
returns a zero exit value (true). It tests the test command
at the start of each iteration. When the test command
returns a zero exit value, the while
command stops executing the set of commands.
Here's the format of the while
command:
while test command
do
other commands
done
The test command
defined in the while
command is the same format as in if-then
statements. As in the if-then
statement, you can use any normal Bash shell command, or you can use the test command
to test for conditions, such as variable values.
The key to the while
command is that the exit status of the test command
specified must change, based on the commands run during the loop. If the exit status never changes, the while
loop will get stuck in an infinite loop.
The most common use of the test command
is to use brackets to check a value of a shell variable that's used in the loop commands:
$ cat sampleScript17.sh
#!/bin/bash
# Trying out the while loop
#
numVar=10
#
while [ $numVar -gt 0 ]
do
echo The current number is $numVar
numVar=$[ $numVar - 1 ]
done
#
exit
$
$ chmod u+x sampleScript17.sh
$
$ ./sampleScript17.sh
The current number is 10
The current number is 9
The current number is 8
The current number is 7
The current number is 6
The current number is 5
The current number is 4
The current number is 3
The current number is 2
The current number is 1
$
The while
command defines the test condition to check for each iteration:
while [ $numVar -gt 0 ]
As long as the test condition is true, the while
command continues to loop through the commands defined. Within the commands, the variable used in the test condition must be modified, or you'll have an infinite loop. In this example, we use shell arithmetic to decrease the variable value by one:
numVar=$[ $numVar - 1 ]
The while
loop stops when the test condition is no longer true.
The until
command works in exactly the opposite way from the while
command. The until
command requires that you specify a test command
that normally produces a nonzero exit value (false). As long as the exit status of the test command
is a nonzero exit value, the Bash shell executes the commands listed in the loop. When the test command
returns a zero exit value (true), the loop stops.
As you would expect, the format of the until
command is as follows:
until test command
do
other commands
done
Only the exit status of the last command determines if the Bash shell executes the other commands defined. The following is an example of using the until
command:
$ cat sampleScript18.sh
#!/bin/bash
# Trying out the until loop
#
numVar=10
#
until [ $numVar -eq 0 ]
do
echo The current number is $numVar
numVar=$[ $numVar - 1 ]
done
#
exit
$
$ chmod u+x sampleScript18.sh
$
$ ./sampleScript18.sh
The current number is 10
The current number is 9
The current number is 8
The current number is 7
The current number is 6
The current number is 5
The current number is 4
The current number is 3
The current number is 2
The current number is 1
$
This example tests the numVar
variable to determine when the until
loop should stop. As soon as the value of the variable is equal to zero, the until
command stops the loop.
Often while writing shell scripts, you'll find yourself using the same code in multiple locations. If it's just a small code snippet, it's usually not that big of a deal. However, rewriting large chunks of code multiple times in your shell script can get tiring and is not good form.
The Bash shell provides a way to help you out by supporting user-defined functions. Functions are blocks of script code that you assign a name to and reuse anywhere in your code. Anytime you need to use that block of code in your script, you simply use the function name you assigned it (referred to as calling the function).
There are two formats you can use to create functions in Bash shell scripts. The first format uses the keyword function
, along with the function name you assign to the block of code:
function name {
commands
}
The name
attribute defines a unique name assigned to the function. Each function you define in your script must be assigned a unique name.
The commands
are one or more Bash shell commands that make up your function. When you call the function, the Bash shell executes each of the commands in the order they appear in the function, just as in a normal script.
The second format for defining a function in a Bash shell script more closely follows how functions are defined in other programming languages:
name() {
commands
}
The empty parentheses after the function name indicate that you're defining a function. The same naming rules apply in this format as in the original shell script function format.
To use a function in your script, specify the function name on a line, just as you would any other shell command:
$ cat sampleScript19.sh
#!/bin/bash
# Trying out functions
#
function func1 {
echo "This is a function example"
}
numVar=5
#
until [ $numVar -eq 0 ]
do
func1
echo The current number is $numVar
numVar=$[ $numVar - 1 ]
done
#
exit
$
$ chmod u+x sampleScript19.sh
$
$ ./sampleScript19.sh
This is a function example
The current number is 5
This is a function example
The current number is 4
This is a function example
The current number is 3
This is a function example
The current number is 2
This is a function example
The current number is 1
$
Each time you reference the func1
function name, the Bash shell returns to the function func1
definition and executes any commands you defined there.
The function definition doesn't have to be the first thing in your shell script, but be careful. If you attempt to use a function before it's defined, you'll get an error message:
$ cat sampleScript20.sh
#!/bin/bash
# Trying out two functions
#
echo "This line is before func1 is defined"
function func1 {
echo "This is a function example"
}
numVar=5
#
until [ $numVar -eq 0 ]
do
func1
echo The current number is $numVar
numVar=$[ $numVar - 1 ]
done
echo "The loop is done"
func2
#
echo "This line is before func2 is defined"
#
function func2 {
echo "This is the second function"
}
#
exit
$
$ chmod u+x sampleScript20.sh
$
$ ./sampleScript20.sh
This line is before func1 is defined
This is a function example
The current number is 5
This is a function example
The current number is 4
This is a function example
The current number is 3
This is a function example
The current number is 2
This is a function example
The current number is 1
The loop is done
./sampleScript20.sh: line 17: func2: command not found
This line is before func2 is defined
$
The first function, func1
, was defined after the first statement in the script, which is perfectly fine. When the func1
function was used in the script, the shell knew where to find it.
However, the script attempted to use the func2
function before it was defined. Because the func2
function wasn't defined, when the script reached the place where we used it, it produced an error message.
You also need to be careful about your function names. Remember, each function name must be unique, or you'll have a problem. If you redefine a function, the new definition overrides the original function definition, without producing any error messages:
$ cat sampleScript21.sh
#!/bin/bash
# Trying out redefining a function
#
function myFunc1 {
echo "This is the first function definition"
}
myFunc1
#
function myFunc1 {
echo "This overrides the first function definition"
}
#
numVar=3
#
until [ $numVar -eq 0 ]
do
myFunc1
echo The current number is $numVar
numVar=$[ $numVar - 1 ]
done
#
exit
$
$ chmod u+x sampleScript21.sh
$
$ ./sampleScript21.sh
This is the first function definition
This overrides the first function definition
The current number is 3
This overrides the first function definition
The current number is 2
This overrides the first function definition
The current number is 1
$
The original definition of the myFunc1
function works fine, but after the second definition of the myFunc1
function, any subsequent uses of the function use the second definition.
Ideally you are now inspired to pursue more shell script writing to make your system admin work a little easier. If you'd like to learn more, one of our favorite resources is the Linux Command Line and Shell Scripting Bible by Richard Blum and Christine Bresnahan (Wiley, 2021).
bash
command to run it. What is most likely the problem?if-then
statements or, if there are only two conditions to check, through the use of Boolean logic to combine the tests.
machine0087.log
file's existence needs to be checked. In addition, a counter variable, logCount
, must be examined as well. If the file exists and the counter has reached 100
, the script should exit. What if-then
statement(s) should you write to make this test as consolidated as possible?for
, while
, and until
.
stop here
. You are reading in the text file lines one at a time, keeping only the line's text, and putting it into the variable fileLine
. What type of loop should you use in your script, and what is the syntax of that loop's first line?Properly write a function namedecho "Error in processing file, $1."
echo "Check that the $1 file exists and contains data."
echo "This script will now exit…"
exit
errorMessage
that will allow you to turn these four lines of code into a single line (except for the function declaration) throughout the script.