Post

Linux pt 6: Bash Scripting (90 Days of DevOps)

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:

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 90DaysOfDevOps is created
  • a new file named Day19 is 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 1 is the interpreter directive (shebang) as seen in the previous script
  • Lines 2 and 3 are comments, as seen in the previous script
  • Lines 5 and 6 are where we declare a couple of Bash variables and set their values.
  • In lines 8 and 9, 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 INTEGER2
  • ne - INTEGER1 is not equal to INTEGER2
  • gt - INTEGER1 is greater than INTEGER2
  • ge - INTEGER1 is greater than or equal to INTEGER2
  • lt - INTEGER1 is less than INTEGER2
  • le - INTEGER1 is less than or equal to INTEGER2

Tests for checking on files/directories:

  • -e FILE - FILE exists
  • -d FILE - FILE exists and is a directory
  • -f FILE - FILE exists and is a regular file
  • -s FILE - FILE exists and has a size greater than zero
  • -r FILE - 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

This post is licensed under CC BY-NC-SA 4.0 by the author.