Introduction to Shell Scripting


Introduction to Shell Scripting: Automate Your World with Bash

Welcome to an exciting journey into the world of Shell Scripting! Have you ever found yourself performing repetitive tasks on your computer, wishing there was a magical way to automate them? Or perhaps you’re a software engineer looking to streamline your development workflow, or an IT student eager to master powerful command-line tools. If so, you’re in the right place!

Today, we’re diving deep into the fundamentals of Bash shell scripting, a skill that transforms you from a computer user into a true command-line wizard. We’ll cover everything from writing your very first script to handling variables, controlling program flow with loops and conditionals, and even building a practical project: a script that monitors your disk space and alerts you when it’s running low. This isn’t just theory; we’re giving you the tools and understanding to start automating your world right now. Get ready to unlock new levels of efficiency and control over your system. Let’s begin!

What is Shell Scripting?

Imagine a bustling digital city, a complex network of servers, workstations, and devices, all humming with activity. Shell scripting is your baton, allowing you to conduct this digital orchestra with precision and power. It’s the art of writing a series of commands in a file that the shell (the command-line interpreter) can execute, automating tasks from simple file operations to complex system administration. This allows you to chain together commands, making your computer perform elaborate sequences of operations with a single instruction.

The most common shell you’ll encounter on Linux and macOS is Bash (Bourne-Again SHell), and it’s what we’ll be focusing on throughout this guide.

Getting Started: Your First Script (“Hello, World!”)

Every journey begins with a single step, and in scripting, that’s often the ‘Hello, World!’ program. To create our first script, we simply open a text editor (like Nano, Vim, or VS Code) and type our commands.

The Shebang Line: #!/bin/bash

The first crucial line in almost any shell script is the ‘shebang’ – #!/bin/bash. This tells the operating system which interpreter to use for running the script. Think of it as the script’s instruction manual, pointing to the Bash shell.

The echo Command

After the shebang, we use the echo command, which is like the print statement in other programming languages, simply displaying text to your terminal.

Here’s what your first script, 01_hello_world.sh, will look like:

#!/bin/bash
#
# ShellScriptingWorkshop/01_hello_world.sh
# A very basic script to print a greeting.

# The 'echo' command is used to display text on the standard output.
echo "Hello, Shell Scripting Workshop!"
echo "This is your first Bash script."

Making it Executable and Running It

Once you’ve saved your file with a .sh extension (e.g., hello_world.sh), you need to give it execution permissions. This is done with the chmod +x hello_world.sh command. It’s like giving your script the ‘go-ahead’ to run.

Finally, to execute it, you simply type ./hello_world.sh in your terminal. The ./ signifies that you want to run the script located in the current directory. Congratulations, you’ve just run your first shell script!

chmod +x 01_hello_world.sh
./01_hello_world.sh

Expected output:

Hello, Shell Scripting Workshop!
This is your first Bash script.

Variables: Storing and Reusing Data

Just like in any programming language, variables are fundamental for storing and manipulating data in shell scripts. Think of them as named containers holding values that your script can use.

Declaring Variables

In Bash, declaring a string variable is as simple as MY_NAME="Alice". Notice there are no spaces around the equals sign! To access the value stored in a variable, you prefix its name with a dollar sign, like echo "Hello, $MY_NAME!". It’s good practice to enclose variable names in double quotes, especially when the value might contain spaces, to prevent unexpected behavior.

Numeric Variables and Arithmetic

Bash also handles numeric variables, though it treats them as strings by default. For arithmetic operations, you’d use a special syntax, like SUM=$((NUM1 + NUM2)). This ((...)) structure tells Bash to perform mathematical calculations.

Array Variables

Beyond single values, Bash allows for array variables, where you can store multiple items in an ordered list, such as FRUITS=("Apple" "Banana" "Cherry"). You access individual elements using their index, starting from zero (e.g., ${FRUITS[0]}), or retrieve all elements with "${FRUITS[@]}".

Here’s an excerpt from 02_variables.sh demonstrating these concepts:

#!/bin/bash
# ... (script header) ...

# --- String Variables ---
MY_NAME="Alice"
GREETING="Hello"
echo "$GREETING, $MY_NAME!"

# --- Numeric Variables ---
NUM1=10
NUM2=5
SUM=$((NUM1 + NUM2))
echo "Sum of $NUM1 and $NUM2 is: $SUM"

# --- Array Variables ---
FRUITS=("Apple" "Banana" "Cherry" "Date")
echo "All fruits: ${FRUITS[@]}"
echo "My favorite fruit is: ${FRUITS[0]}"

Conditionals: Making Your Scripts Smart (If/Else/Elif)

To create intelligent scripts that respond to different conditions, we use conditionals. These are the ‘if this, then that’ statements of programming. The most common form is the if-else statement.

if, elif, else Structure

In Bash, you typically use [[ ... ]] for conditional expressions. For example, to check if a number is greater than 10, you’d write if [[ $NUM -gt 10 ]]; then ... fi. Here, -gt means ‘greater than’. We also have -lt for ‘less than’, -eq for ‘equal to’, and so on.

For more complex scenarios, the elif (else if) statement allows you to test multiple conditions sequentially, like checking if a number is positive, negative, or zero.

String and File Comparisons

String comparisons are also straightforward: [[ "$NAME" == "John" ]] checks for equality, and != checks for inequality. Bash provides powerful checks for strings, like -z to see if a string is empty, and -n to see if it’s not empty.

Furthermore, you can test for file existence and types: -f checks if a path is a regular file, -d for a directory, and -e for any existing entity. These conditional statements are the backbone of decision-making in your scripts, allowing them to adapt to various situations.

Here’s an excerpt from 03_conditionals.sh showcasing these checks:

#!/bin/bash
# ... (script header) ...

# --- Example 1: Basic if statement ---
NUM=15
if [[ $NUM -gt 10 ]]; then
  echo "$NUM is greater than 10."
fi

# --- Example 2: if-else statement ---
NUM=7
if [[ $((NUM % 2)) -eq 0 ]]; then
  echo "$NUM is an even number."
else
  echo "$NUM is an odd number."
fi

# --- Example 3: if-elif-else statement ---
NUM=-5
if [[ $NUM -gt 0 ]]; then
  echo "$NUM is a positive number."
elif [[ $NUM -lt 0 ]]; then
  echo "$NUM is a negative number."
else
  echo "$NUM is zero."
fi

# --- Example 4: String comparison ---
NAME="John"
if [[ "$NAME" == "John" ]]; then
  echo "Hello, John!"
fi

# --- Example 5: File existence checks ---
FILE_PATH="./01_hello_world.sh"
if [[ -f "$FILE_PATH" ]]; then
  echo "$FILE_PATH is a regular file."
fi

Loops: Automating Repetitive Tasks (For & While)

Repetitive tasks are a perfect candidate for automation, and loops are your best friends here. Bash offers powerful for and while loops to handle such scenarios.

The for Loop

One common use is iterating over a range of numbers, like for i in {1..5}; do echo "Count: $i"; done. Another powerful application is iterating over a list of items, such as an array of strings: for fruit in "${FRUITS[@]}"; do echo "I like $fruit."; done.

for loops can also iterate over the output of a command or even a set of files matching a pattern. This flexibility makes for loops indispensable for batch processing and automating file operations.

The while Loop

While for loops are great for iterating over known sets, while loops are perfect for situations where you want to repeat actions as long as a certain condition remains true. A classic example is a simple counter: COUNTER=1; while [[ $COUNTER -le 5 ]]; do echo "Counter is: $COUNTER"; COUNTER=$((COUNTER + 1)); done.

A more powerful application of the while loop is reading a file line by line, which is incredibly useful for processing configuration or log files.

while loops also give you control over their flow with break (exits the loop) and continue (skips the rest of the current iteration).

An excerpt from 04_loops.sh illustrates these loops:

#!/bin/bash
# ... (script header) ...

# --- For Loop Example 1: Iterating over a range of numbers ---
echo "--- For Loop (Numeric Range) ---"
for i in {1..5}; do
  echo "Count: $i"
done

# --- For Loop Example 2: Iterating over a list of items (strings) ---
FRUITS=("Apple" "Banana" "Cherry" "Date")
for fruit in "${FRUITS[@]}"; do
  echo "I like $fruit."
done

# --- While Loop Example 1: Basic counter ---
echo -e "\n--- While Loop (Counter) ---"
COUNTER=1
while [[ $COUNTER -le 5 ]]; do
  echo "Counter is: $COUNTER"
  COUNTER=$((COUNTER + 1))
done

# --- While Loop Example 2: Reading a file line by line ---
echo "Line 1" > temp_file.txt
echo "Line 2" >> temp_file.txt
while IFS= read -r line; do
  echo "File Line: $line"
done < temp_file.txt
rm temp_file.txt

Functions: Organizing Your Code

As your scripts grow in complexity, organizing your code into reusable blocks becomes essential. This is where functions come in. Functions allow you to encapsulate a set of commands that perform a specific task, making your scripts more modular, readable, and maintainable.

Defining and Calling Functions

Defining a function is straightforward: function greet() { echo "Hello!"; } or simply greet() { echo "Hello!"; }. You can then call the function by its name, greet, anywhere in your script.

Arguments and Scope

Functions can also accept arguments, much like command-line tools. These are accessed inside the function using special variables like $1 for the first argument, $2 for the second, and "$@" to refer to all arguments.

A crucial concept within functions is variable scope. By default, variables in Bash are global. However, using the local keyword, like local my_var="value", creates variables that are only accessible within that specific function, preventing unintended side effects.

Here’s an excerpt from 05_functions.sh:

#!/bin/bash
# ... (script header) ...

# --- Function Example 1: Simple function without arguments ---
function greet() {
  echo "Hello from the greet function!"
}
echo "Calling 'greet' function:"
greet

# --- Function Example 2: Function with arguments ---
print_arguments() {
  echo "Function 'print_arguments' received $# arguments."
  echo "First argument: $1"
  echo "All arguments: $@"
}
echo "Calling 'print_arguments' function:"
print_arguments "Apple" "Banana" "Cherry"

# --- Function Example 3: Function with local variables ---
calculate_sum() {
  local num1=$1
  local num2=$2
  local sum=$((num1 + num2))
  echo "The sum is: $sum"
}
echo "Calling 'calculate_sum' function:"
calculate_sum 20 30

# --- Function Example 4: Function returning a value (using 'echo') ---
get_square() {
  local number=$1
  echo $((number * number))
}
RESULT=$(get_square 7)
echo "The square of 7 is: $RESULT"

Project Spotlight: Automated Disk Space Monitor

Now that we've covered the fundamentals, let's bring it all together with a practical beginner project: a Disk Space Monitor script. Imagine a scenario where you're managing a server or even your own development machine, and you need to know if your disk space is getting critically low before it impacts performance or causes system failures.

Our disk_monitor.sh script is designed to do exactly that: it will check the disk usage of a specified partition and, if it exceeds a predefined threshold, send you a warning notification.

The Problem

Manually checking disk space using commands like df -h is fine for a one-off check, but it's tedious and reactive. You only know there's a problem when you run the command. For critical systems, you need proactive alerting.

The Solution: disk_monitor.sh

This project demonstrates how real-world automation tasks can be built using variables, conditionals, command execution, and even external tools like email clients. What makes this script particularly flexible is its reliance on a separate configuration file, config.conf. This file allows you to easily customize parameters like the warning threshold, the recipient for email alerts, and the specific disk partition to monitor, all without modifying the core script itself. This separation of concerns is a best practice in software development, making your scripts more reusable and easier to maintain.

config.conf (Configuration File)

This simple file sets up your monitoring preferences:

# ShellScriptingWorkshop/config.conf
# Configuration file for the disk_monitor.sh script.

# DISK_THRESHOLD: Percentage of disk usage that triggers a warning.
# Example: 80 means 80% or higher will trigger a warning.
DISK_THRESHOLD="80"

# EMAIL_RECIPIENT: Email address to send warnings to.
# If left empty, no email will be sent.
# Ensure 'mailx' or 'mailutils' is installed on your system for email functionality.
EMAIL_RECIPIENT="your_email@example.com" # <--- IMPORTANT: Change this to a real email!

# PARTITION_TO_CHECK: The mount point of the partition to monitor.
# Use 'df -h' to find your desired partition (e.g., '/', '/home', '/var').
PARTITION_TO_CHECK="/"

disk_monitor.sh (The Core Script)

The script begins by loading its configuration using the source command. It then uses powerful command-line tools:

  • df -h: Shows disk space usage in human-readable format.
  • grep: Filters the output to find the relevant partition.
  • awk: Extracts specific columns (like usage percentage and mount point).
  • tr -d %: Removes the percentage sign to allow numeric comparison.

Finally, it compares the extracted usage value against your configured threshold. If the usage is too high, it constructs an informative email and sends it using the mail command (which requires a mail client like mailutils or mailx to be installed on your system).

Here's a snippet of the core logic:

#!/bin/bash
# ... (script header and config loading) ...

# Get disk usage for the specified partition.
DISK_INFO=$(df -h "$PARTITION_TO_CHECK" 2>/dev/null | awk 'NR==2 {print $5 " " $6}' | head -n 1)

if [[ -z "$DISK_INFO" ]]; then
  echo "Error: Could not retrieve disk information for '$PARTITION_TO_CHECK'."
  # ... (error handling and email) ...
  exit 1
fi

read -r USAGE_PERCENT MOUNT_POINT <<< "$DISK_INFO"
USAGE_VALUE=$(echo "$USAGE_PERCENT" | tr -d '%')

echo "Current usage for $MOUNT_POINT: $USAGE_VALUE%"

# Compare current usage with the threshold.
if [[ "$USAGE_VALUE" -ge "$DISK_THRESHOLD" ]]; then
  echo "WARNING: Disk usage on $MOUNT_POINT is at $USAGE_VALUE%, which is at or above the threshold of $DISK_THRESHOLD%!"
  
  SUBJECT="DISK SPACE ALERT: $MOUNT_POINT Usage at ${USAGE_VALUE}% on $(hostname)"
  BODY="High disk usage detected on ${MOUNT_POINT}.\\n\\n"
  BODY+="Current usage: ${USAGE_VALUE}%\\n"
  # ... (more body content) ...

  send_email_notification "$SUBJECT" "$BODY" "$EMAIL_RECIPIENT"
else
  echo "Disk usage on $MOUNT_POINT is acceptable ($USAGE_VALUE% < $DISK_THRESHOLD%). No warning needed."
fi

# ... (cron job instructions) ...

How to Use the Disk Space Monitor

  1. Clone the Repository: Get the code onto your machine.
  2. Navigate: cd ShellScriptingWorkshop
  3. Make Executable: chmod +x disk_monitor.sh
  4. Configure: Open config.conf (e.g., nano config.conf) and set your EMAIL_RECIPIENT and PARTITION_TO_CHECK.
  5. Install Mail Client (if needed): For email warnings, you might need to install a mail client like 'mailx' or 'mailutils'. For example, on Debian/Ubuntu: sudo apt-get install mailutils. On CentOS/RHEL: sudo yum install mailx.
  6. Run: ./disk_monitor.sh
  7. Automate with Cron: To run it regularly, add it to your cron jobs. Run crontab -e and add a line like:
    0 * * * * /path/to/ShellScriptingWorkshop/disk_monitor.sh >> /var/log/disk_monitor.log 2>&1
    This would run the script every hour and log its output.

Putting It All Together: The Video Walkthrough

For a complete, step-by-step walkthrough of these concepts and a live demonstration of building the Disk Space Monitor script, be sure to watch our accompanying YouTube video:

Watch on YouTube: Introduction to Shell Scripting

Access the Code

All the code examples discussed in this blog post, along with the complete Disk Space Monitor project, are available in our GitHub repository. We encourage you to clone it, experiment with the scripts, and even contribute your own improvements!


<> Explore the Code on GitHub

Conclusion

And there you have it! From a simple 'Hello, World!' to a sophisticated disk space monitor, you've now explored the essential building blocks of shell scripting. You've learned how to create and execute scripts, manage data with variables, control flow with powerful if-else and for/while loops, and organize your code with functions. Most importantly, you've seen how these individual concepts combine to create practical, automated solutions for real-world problems.

The disk_monitor.sh project is just one example; the principles you've learned can be applied to countless other automation tasks, whether it's backing up files, deploying applications, or managing complex server environments. This is just the beginning of your journey. Happy scripting!

If you found this tutorial helpful, please consider sharing it with your network and subscribing to our channel for more valuable technical content. Your support helps us create more resources for the community!

Stay tuned for more deep dives into automation and software development!


Posted in

Leave a Reply

Discover more from Modern Java developement with Devops and AI

Subscribe now to keep reading and get access to the full archive.

Continue reading