Linux pt 6: Bash Scripting (90 Days of DevOps)
So far in this blog’s coverage of the Linux portion of the “90 Days of DevOps” challenge:
- We have created a Vagrant virtual machine running Debian Linux,
- We connected to that VM using SSH, and went on a tour of many essential Linux commands.
- We worked with software package management, the Linux filesystem layout, and storage.
- We compared the
nanoandvimcommand-line text editors. - We manually configured the SSH service.
- We built a basic web server using the LAMP stack.
In this post, we will have a look scripting using Bash.
Bash has been around since the late 1980s. Modern scripting languages are often a more appropriate choice for system automation purposes. However, Bash scripting is useful within Linux largely due to the fact that in any given Linux environment, the odds of the Bash shell being already available are very high.
Also, Bash calls Linux OS commands directly, which can be useful.
(Re)connecting to the Linux VM
If our VM is not currently running, we start it:
1
$ vagrant up
Then we can reconnect to it using:
1
$ vagrant ssh
Getting Started
Once connected, we create a new file for our Bash script.
1
vagrant@bookworm:~$ touch bash_script_1.sh
Then, we edit the file using our text editor of choice.
1
2
3
4
5
6
7
8
9
#!/usr/bin/env bash
# bash_script_1
# This script is to demonstrate bash scripting!
mkdir 90DaysOfDevOps
cd 90DaysOfDevOps
touch Day19
ls
Below we look at the contents of this script, line-by-line.
Shebang ( !# ) / interpreter directive
Line 1 above, #!/usr/bin/env bash, begins with what is commonly called the “shebang” or “hashbang.”
This !# belongs at the start of every Bash script. It signals that the rest of the line is an interpreter directive. What we write immediately after #! on this line helps the target Linux system running our script to find the Bash shell, which will be needed to interpret the rest of the script.
Along with #!/usr/bin/env bash, another commonly-used interpreter directive in Bash scripts is #!/bin/bash. Whatever comes after the #! should resolve to the location of the Bash shell on the targeted Linux system(s).
The directive used in our script might be considered portable across more systems since it uses the env command to find the location of bash at runtime, rather than hard-coding a specific path to bash . Of course, this choice introduces its own hard-coded assumption (that the env command itself is in /usr/bin/) .
Either way, both directives are commonly used in Bash scripts, so they are both mentioned here.
Comments ( # )
Lines 2 and 3 in our script,
1
2
# bash_script_1
# This script is to demonstrate bash scripting!
start with a # (as opposed to #! ). This marks them as Bash comments. As a result, these lines won’t be interpreted by Bash and are used only for documentation purposes.
A script’s name, and a description of what it is for, are commonly included in Bash scripts as comments.
Executable contents
After the comments comes the main body of our script:
1
2
3
4
mkdir 90DaysOfDevOps
cd 90DaysOfDevOps
touch Day19
ls
In this case, it’s a simple list of actual Linux commands to be read and executed in order when our script runs.
Setting execute permission on the script
Once the file is saved to match the contents shown above, we need to change its file permissions on our system so that Linux considers it executable:
1
vagrant@bookworm:~$ chmod +x bash_script_1.sh
Then we can run the script.
Running the script
1
2
vagrant@bookworm:~$ ./bash_script_1.sh
Day19
Notice above that we type ./ before the script’s filename when running it. This is important because our script file is not saved in any of the predefined directories where Linux would search for executable files (aka those included in the system’s PATH environment variable).
By typing ./ before our script’s name, we explicitly specify that the script we want to run is saved in the current directory. Without doing that, we would have received an error:
1
2
vagrant@bookworm:~$ bash_script_1.sh
-bash: bash_script_1.sh: command not found
The result
As requested by the script,
- a new subdirectory named
90DaysOfDevOpsis created - a new file named
Day19is created within that subdirectory - the contents of the new subdirectory are listed
If we wanted to, we could verify the above changes using our terminal.
1
2
3
4
5
vagrant@bookworm:~$ ls
90DaysOfDevOps bash_script_1.sh
vagrant@bookworm:~$ ls 90DaysOfDevOps/
Day19
As mentioned earlier, this is a very basic form of script, where commands are simply listed in order for the Bash shell to execute.
Next we’ll take advantage of some of the programming logic available to Bash which can make our script more versatile. The available logic is similar to what we saw earlier when working with the Go programming language (and what we’d find across programming languages in general).
Variables, conditional tests
For the next example, we’ll create a second script:
1
vagrant@bookworm:~$ touch bash_script_2.sh
Variables
Variables allow us to store values. Below is an example of a Bash script using variables which we will save into this file:
1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env bash
# bash_script_2
# This script is to demonstrate the usage of variables and conditionals in Bash.
ChallengeName=90DaysofDevOps
TotalDays=90
echo "Welcome to the $ChallengeName challenge."
echo "It involves $TotalDays days of learning stuff about stuff!"
Going over this script line by line,
- Line
1is the interpreter directive (shebang) as seen in the previous script - Lines
2and3are comments, as seen in the previous script - Lines
5and6are where we declare a couple of Bash variables and set their values. - In lines
8and9, we reference the values of the variables by typing$before each variable’s name.
As with the previous example, we need to make the script executable before we can run it:
1
vagrant@bookworm:~$ chmod +x bash_script_2.sh
When we run the script, it uses the echo command to output our specified text, while referencing the values we set for the two Bash variables.
1
2
3
vagrant@bookworm:~$ ./bash_script_2.sh
Welcome to the 90DaysofDevOps challenge.
It involves 90 days of learning stuff about stuff!
Conditional tests
Below is an updated example of the script which adds conditional logic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env bash
# bash_script_2
# This script is to demonstrate the usage of variables and conditionals in Bash.
ChallengeName=90DaysofDevOps
TotalDays=90
# User Input
echo "Enter Your Name:"
read name
echo "Welcome $name to $ChallengeName."
echo "How Many Days of the $ChallengeName challenge have you completed?"
read DaysCompleted
if [ $DaysCompleted -eq 90 ]
then
echo "You have finished. Well done!"
elif [ $DaysCompleted -lt 90 ]
then
echo "Keep going. You are doing great!"
else
echo "You have entered the wrong amount of days."
fi
Notice how this version of the script contains an if clause starting at line 15. After using read to get user input on line 13, the script takes advantage of Bash conditional logic to determine what to output next.
For a handy summary of conditions available for use within if clauses in Bash scripts, you can run the Linux command man test . Below are some examples taken from that man page. All of these conditions return either TRUE or FALSE:
Tests for comparing numerical values:
eq- INTEGER1 is equal to INTEGER2ne- INTEGER1 is not equal to INTEGER2gt- INTEGER1 is greater than INTEGER2ge- INTEGER1 is greater than or equal to INTEGER2lt- INTEGER1 is less than INTEGER2le- INTEGER1 is less than or equal to INTEGER2
Tests for checking on files/directories:
-eFILE - FILE exists-dFILE - FILE exists and is a directory-fFILE - FILE exists and is a regular file-sFILE - FILE exists and has a size greater than zero-rFILE - FILE exists and the user has read access
So if we wanted to add a check to our script to verify whether or not a specific file is already present on the system running the script, we could add a block like:
1
2
3
4
5
6
7
8
9
10
FILE="90DaysOfDevOps.txt"
if [ -f "$FILE" ]
then
# what to do if the file exists
echo "$FILE is a file."
else
# what to do otherwise
echo "$FILE is not a file."
fi
Using the tools covered throughout this post, we will attempt to create a useful script below.
Example scenario: Bash script for creating Linux accounts
For this next script, we want to automate the process of creating each new Linux user account on a system.
Scenario requirements
What we want in this script:
- The person running the script should provide the desired Linux username and password at runtime.
- The script should create a Linux account on the system where the it is run, using the information provided.
- Once finished, the script should output a confirmation message that the Linux user account has been successfully created.
The script
Below is the script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#! /usr/bin/env bash
# bash_create_user.sh
# Custom script to create Linux user accounts
echo "What will be the username for the new user account?"
read username
echo "What will be the password?"
read -s password # -s to prevent showing input in terminal
echo "Creating user account named $username."
# Create a user account on this system, using $username entered earlier
sudo useradd -m $username
# Update the user account, setting its password to $password entered earlier
sudo chpasswd <<< $username:$password
We make this script file executable, and proceed to run it:
1
2
3
4
5
6
7
vagrant@bookworm:~$ chmod +x bash_create_user.sh
vagrant@bookworm:~$ ./bash_create_user.sh
What will be the username for the new user account?
pfry
What will be the password?
Creating user account named pfry.
After providing our desired input, we see that our requirements for this script have been met.
A real-world script for user account creation would almost certainly involve more more test conditions (e.g. to check for any arguments that might have passed to the script, or to handle errors), but the above is enough to demonstrate some basic Bash scripting.
See you in the next post!
<< Back to 90 Days of DevOps posts
<<< Back to all posts