Bash Scripting for Software Engineers - A Beginner's Guide
Tags: programming, bash, shell, linux
Published: 2024-06-09 15:00:00

It's your first day at your new job. You've been handed a computer running Linux, and you were told to locate all the files containing the word "key". It's a simple enough task, right? The catch is, there are thousands of files on that system, and you've never written a shell script before. 

Shell scripting, the language of the command line, is your ticket to automating repetitive tasks and mastering your Linux environment. With this guide, you'll be able to navigate your way around any Bash terminal with ease, and you might learn a couple of cool tricks along the way!

## What are all these terms, anyway?

### Shell

A shell is a program that interprets commands and executes them on your operating system. Simply put, a shell is a command-line interpreter that acts as a bridge between you and your operating system. It is usually run in a terminal or console. The terminal provides an input interface to run textual commands, and the shell takes in those commands and executes them on your system.

### Bash

It is a shell program and command language. Bash has its roots in earlier Unix shells. The **Bourne shell**, released in 1977, was a major step forward in allowing users to write scripts and automate tasks. Bash, short for **Bourne-Again SHell**, was created in 1989 by Brian Fox as part of the GNU Project. It was designed as a free and improved alternative to the Bourne shell, while still maintaining compatibility with existing scripts.

## Getting started with Bash Scripting

### Setting up the Development Environment
Bash shells are commonly found on Linux operating systems. In this article, we will be working primarily with Ubuntu, a Linux distribution. You can download and set up Ubuntu here: (Canonical Ubuntu)[http://ubuntu.com/download].

Alternatively, if you're working from a Windows environment, you can download the Windows Subsystem for Linux (WSL) which gives you access to a Linux operating system and a bash terminal, without the need for dual booting, or clean wiping Windows. You can get WSL here: https://learn.microsoft.com/en-us/windows/wsl/install.

Once you have your terminal open, you should see a prompt like the one below:
```bash
$
```

Now we are ready to begin.

### Basic Shell commands

- `cd`: Change directory. This command is used to navigate to a different directory within the file system.
```bash
$ cd Desktop
# This will switch to the ./Desktop directory.
```

- `ls`: List directory contents. It displays the files and directories in the current directory.
```bash
$ ls
file1.txt
file2.txt
directory1/
```

- `mkdir`: Make a directory. This command creates a new directory with the given name.
```bash
$ mkdir newDir
$ ls
newDir
```

- `rm`: Remove. It is used to delete files or directories. Be cautious as it does not have a confirmation prompt by default.
```
$ rm newFile
$ ls
```

```bash
$ rm -rf newDir
$ ls
```

- `pwd`: Print working directory. It shows the current directory path.
```
$ pwd
/home/alexin/newDir/
```
- `cp`: Copy. This command is used to copy files or directories from one location to another.
```bash
$ cp ../file_to_copy.txt .
$ ls
file_to_copy.txt
```

- `mv`: Move. It moves files or directories from one location to another. It can also be used to rename files.
```bash
$ mv ../file_to_move.txt .
$ ls
file_to_move.txt
```

- `touch`: Create a new file. It creates an empty file with the specified name or updates the timestamp of an existing file.
```bash
$ touch newFile.txt
$ ls
newFile.txt
```

- `cat`: Concatenate and display content. This command is used to display the content of files on the terminal. It can also be used to concatenate and display multiple files.
```bash
$ cat newFile.txt
This is the content of newFile.txt.
```

- `echo`: Print text. This is used to print text or variables to the terminal or standard output.
```bash
$ echo 'I am Alexin'
I am Alexin
```

```bash
$ name="Alexin"
$ echo 'My name is $name'
My name is Alexin
```

- `man`: Displays information about a command. 'man' stands for manual, and it is used to provide detailed information about the specified command, including its purpose, syntax, options, and examples of usage.
```bash
$ man ls
LS(1)                            User Commands                           LS(1)

NAME
       ls - list directory contents

SYNOPSIS
       ls [OPTION]... [FILE]...

DESCRIPTION
       List  information  about  the FILEs (the current directory by default).
       Sort entries alphabetically if none of -cftuvSUX nor --sort  is  speci‐
       fied.

       Mandatory  arguments  to  long  options are mandatory for short options
       too.

       -a, --all
              do not ignore entries starting with .

       -A, --almost-all
              do not list implied . and ..

       --author
 Manual page ls(1) line 1 (press h for help or q to quit)
```

- `grep`: The `grep` command is used to search for specific patterns or regular expressions within files or streams of text. It stands for "Global Regular Expression Print".
```bash
$ grep "error" file.txt
This line contains error.
This line also contains the word "error".
This line has errors (error is in the word errors)
```

- `find`: Search files and directories. This command lets you search for matching expressions or patterns in a specified file or directory. It allows you to search based on various criteria such as name, type, size, and permissions.
```bash
$ find / -
```

## Creating Bash Scripts

A bash script is a file typically ending with the extension `.sh` that contains a logical series of related commands to be executed.  You can create a bash script using the nano text editor by running this command in your terminal:

```bash
$ nano new_script.sh
```

In the editor, start your script with a **shebang** line. The shebang line tells the system that this file is a script and specifies the interpreter to use.

```bash
#!/bin/bash
```

You can add a simple command to print the text "Hello, World!" to the terminal.

```bash
#!/bin/bash

text="Hello, World!"
echo $text
```

Save the file then exit: `CTRL X` + `Y` + `Enter`.

Before you can run the file, you have to make it executable. Change the file's permissions using the following commands:

```bash
$ chmod u+x new_script.sh
```

Now run your bash script.
```bash
$ ./new_script.sh
Hello, World!
```

Congratulations! You have created your first bash script. Now, let's learn about handling control flow with conditionals and loops.

### Control flow

This refers to the order in which commands are executed in a program. In Bash scripting, control flow constructs allow you to manage the execution sequence of your script, enabling you to make decisions, repeat actions, and manage complex logical conditions.

#### Key Control Flow Constructs in Bash

1. **Conditional Statements**: These are used to execute a block of code only if a specified condition is true.

   - **if Statement**:
     ```bash
     if [ condition ]; then
       # Code to execute if condition is true
     fi
     ```

   - **if-else Statement**:
     ```bash
     if [ condition ]; then
       # Code to execute if condition is true
     else
       # Code to execute if condition is false
     fi
     ```

   - **if-elif-else Statement**:
     ```bash
     if [ condition1 ]; then
       # Code to execute if condition1 is true
     elif [ condition2 ]; then
       # Code to execute if condition2 is true
     else
       # Code to execute if neither condition1 nor condition2 is true
     fi
     ```

2. **Loops**: These are used to repeat a block of code multiple times.

   - **for Loop**:
     ```bash
     for variable in list; do
       # Code to execute for each item in list
     done
     ```

   - **while Loop**:
     ```bash
     while [ condition ]; do
       # Code to execute as long as condition is true
     done
     ```

   - **until Loop**:
     ```bash
     until [ condition ]; do
       # Code to execute until condition is true
     done
     ```

3. **Case Statement**: This is used to execute one of several blocks of code based on the value of a variable.

   ```bash
   case $variable in
     pattern1)
       # Code to execute if variable matches pattern1
       ;;
     pattern2)
       # Code to execute if variable matches pattern2
       ;;
     *)
       # Code to execute if variable doesn't match any pattern
       ;;
   esac
   ```

To demonstrate these concepts, let us write a program that performs a calculation given two numbers and an operation as input:

```bash
#!/bin/bash
# Function to perform arithmetic operations
perform_operation() {
  # First we define 3 variables, to store the arguments passed into the function.
  local num1=$1
  local num2=$2
  local operation=$3

  # We then use a case statement to execute an arithmetic operation, based on the value of the $operation variable. We log the result of the operation to the terminal.
  case $operation in
    addition)
      result=$((num1 + num2))
      echo "Result of addition: $result"
      ;;
    subtraction)
      result=$((num1 - num2))
      echo "Result of subtraction: $result"
      ;;
    multiplication)
      result=$((num1 * num2))
      echo "Result of multiplication: $result"
      ;;
    division)
      if [ $num2 -eq 0 ]; then
        echo "Error: Division by zero is not allowed."
      else
        result=$((num1 / num2))
        echo "Result of division: $result"
      fi
      ;;
    *)
      echo "Invalid operation. Please use one of the following: addition, subtraction, multiplication, division."
      ;;
  esac
}

# Main script starts here
echo "Enter the first number:"
read num1

echo "Enter the second number:"
read num2

echo "Enter the operation (addition, subtraction, multiplication, division):"
read operation

# Perform the operation
perform_operation $num1 $num2 $operation

# Loop to check if user wants to perform another operation
while true; do
  echo "Do you want to perform another operation? (yes/no)"
  read choice

  case $choice in
    yes|y|Yes|YES)
      echo "Enter the first number:"
      read num1

      echo "Enter the second number:"
      read num2

      echo "Enter the operation (addition, subtraction, multiplication, division):"
      read operation
      # The read command enables a shell script to read user input from the command line.

      perform_operation $num1 $num2 $operation
      ;;
    no|n|No|NO)
      echo "Exiting the script. Goodbye!"
      break
      ;;
    *)
      echo "Invalid choice. Please enter yes or no."
      ;;
  esac
done
```

#### Handling command line arguments

n a Bash script, command-line arguments are accessed using positional parameters:
- `$0` is the name of the script.
- `$1`, `$2`, ..., `$N` are the arguments passed to the script.
- `$#` is the number of arguments passed to the script.
- `$@` is all the arguments passed to the script.
- `$*` is all the arguments passed to the script as a single word.
- `"$@"` is all the arguments passed to the script, individually quoted.
- `"$*"` is all the arguments passed to the script, quoted as a single word.

Let's modify the previous arithmetic script to handle command-line arguments. This way, users can specify the numbers and the operation directly when they run the script.

```bash
#!/bin/bash

# Function to perform arithmetic operations
perform_operation() {
  local num1=$1
  local num2=$2
  local operation=$3

  case $operation in
    addition)
      result=$((num1 + num2))
      echo "Result of addition: $result"
      ;;
    subtraction)
      result=$((num1 - num2))
      echo "Result of subtraction: $result"
      ;;
    multiplication)
      result=$((num1 * num2))
      echo "Result of multiplication: $result"
      ;;
    division)
      if [ $num2 -eq 0 ]; then
        echo "Error: Division by zero is not allowed."
      else
        result=$((num1 / num2))
        echo "Result of division: $result"
      fi
      ;;
    *)
      echo "Invalid operation. Please use one of the following: addition, subtraction, multiplication, division."
      ;;
  esac
}

if [ $# -ne 3 ]; then
  echo "Usage: $0 <num1> <num2> <operation>"
  echo "Example: $0 5 3 addition"
  exit 1
fi

num1=$1
num2=$2
operation=$3

perform_operation $num1 $num2 $operation
```

Now that you know the basics of scripting in shell, try out these practice assignments:

### Assignment #1: Locate Files Containing the Word "key"

Your task is to write a Bash script that searches for all files containing the word "key" within a specified directory and its subdirectories. The script should:

1. Accept the directory path as a command-line argument.
2. Use the `grep` command to search for files containing the word "key".
3. Print the paths of the files that contain the word "key".

### Assignment #2: Automate Git Commits for Each Edited File

In this assignment, you will write a Bash script to automate the process of creating a separate Git commit for every file that has been edited in a Git repository. This script will be particularly useful in scenarios where you want to commit each file separately, perhaps for clearer version history or to adhere to specific project guidelines.

1. Ensure the script is executed within a Git repository.
2. Use Git commands to list files that have been edited.
3. Loop through the list of modified files and create a commit for each one.

## Conclusion

In summary, this guide has equipped you with the foundational knowledge to navigate the world of Bash scripting. You've explored core concepts like shell commands, creating scripts, control flow structures, and handling user input. With this strong base, you can now venture into more complex scripting tasks to automate various processes and streamline your workflow on Linux systems.

Remember, practice is key to mastering any skill. Experiment with the provided assignments and explore other scripting challenges to solidify your understanding. Thank you for reading!