Chapter 10: Introduction to Shell Scripting

The Gateway to Automation

Picture yourself standing at the threshold of a new dimension in Linux mastery. Behind you lies the familiar territory of individual commands, each executed one by one like stepping stones across a stream. Ahead stretches the vast landscape of shell scripting—a realm where commands dance together in choreographed sequences, where repetitive tasks bow to the power of automation, and where the true potential of the Linux command line unfolds like a digital symphony.

Shell scripting represents the evolutionary leap from being a mere user of the Linux system to becoming its conductor. It's the art of weaving together the threads of individual commands into a tapestry of automated solutions that can think, decide, and act on your behalf. In this chapter, we'll embark on a journey that transforms you from someone who types commands to someone who crafts intelligent scripts that work tirelessly in the background.

Understanding the Shell Script Ecosystem

What is Shell Scripting?

A shell script is essentially a text file containing a sequence of commands that the shell can execute. Think of it as a recipe for the computer—a step-by-step instruction manual that tells the system exactly what to do, when to do it, and how to handle different scenarios that might arise during execution.

#!/bin/bash

# This is our first shell script

echo "Welcome to the world of shell scripting!"

echo "Today's date is: $(date)"

echo "You are logged in as: $(whoami)"

The beauty of shell scripting lies in its simplicity and power. Unlike complex programming languages that require compilation, shell scripts are interpreted line by line, making them perfect for system administration tasks, automation, and rapid prototyping of solutions.

The Anatomy of a Shell Script

Every well-crafted shell script follows a structure as precise as a master architect's blueprint. Let's dissect the essential components:

The Shebang Line

#!/bin/bash

The shebang (#!) is like a passport that tells the system which interpreter should execute the script. It's the first line that declares, "I am a Bash script, and I should be run by the Bash shell." This seemingly simple line is crucial—without it, the system might not know how to properly execute your script.

Note: The shebang must be the very first line of your script, with no spaces before the #! characters.

Comments: The Script's Documentation

# This script demonstrates basic shell scripting concepts

# Author: Your Name

# Date: Today's Date

# Purpose: Educational demonstration

Comments are the silent guardians of code clarity. They don't affect execution but serve as breadcrumbs for future you (or other developers) to understand the script's purpose and logic. In shell scripting, comments begin with the # symbol.

Variables: The Script's Memory

#!/bin/bash

# Variable demonstration script

 

# String variables

name="Alice"

greeting="Hello"

 

# Numeric variables

age=25

year=2024

 

# Using variables

echo "$greeting, $name! You are $age years old."

echo "The current year is $year"

Variables in shell scripting are like labeled containers that store information. They can hold text (strings), numbers, or even the output of commands. The syntax is elegantly simple: variable_name=value (note: no spaces around the equals sign).

Command Explanation:

- name="Alice": Creates a string variable named 'name' with value "Alice"
- echo "$greeting, $name!": Uses variables within double quotes to allow variable expansion
- Variables are referenced with the $ symbol: $variable_name or ${variable_name}

Your First Shell Script Adventure

Let's create a practical script that demonstrates the fundamental concepts. Imagine you're a system administrator who needs to create a daily system report.

#!/bin/bash

# Daily System Report Generator

# This script creates a comprehensive system status report

 

echo "=========================================="

echo " DAILY SYSTEM REPORT"

echo "=========================================="

echo ""

 

# System information

echo "Report generated on: $(date)"

echo "System hostname: $(hostname)"

echo "Current user: $(whoami)"

echo ""

 

# System uptime

echo "System Uptime:"

uptime

echo ""

 

# Disk usage

echo "Disk Usage Summary:"

df -h | head -5

echo ""

 

# Memory information

echo "Memory Usage:"

free -h

echo ""

 

# Current processes

echo "Top 5 CPU-consuming processes:"

ps aux --sort=-%cpu | head -6

echo ""

 

echo "=========================================="

echo " REPORT COMPLETE"

echo "=========================================="

Creating and Running Your Script:

  1. Create the script file:

nano daily_report.sh

  1. Make it executable:

chmod +x daily_report.sh

  1. Run the script:

./daily_report.sh

Command Explanations:

- nano daily_report.sh: Opens the nano text editor to create/edit the script file
- chmod +x daily_report.sh: Grants execute permission to the script file
- ./daily_report.sh: Executes the script from the current directory

Command Substitution: Capturing Command Output

One of the most powerful features of shell scripting is command substitution—the ability to capture the output of a command and use it as a variable or within another command.

#!/bin/bash

# Command substitution demonstration

 

# Method 1: Using $() - preferred modern syntax

current_date=$(date +"%Y-%m-%d")

file_count=$(ls -1 | wc -l)

current_directory=$(pwd)

 

# Method 2: Using backticks (older syntax)

user_name=`whoami`

system_name=`hostname`

 

echo "Today is: $current_date"

echo "Current directory: $current_directory"

echo "Files in current directory: $file_count"

echo "Logged in as: $user_name on system: $system_name"

 

# Advanced example: Creating timestamped backup

backup_name="backup_$(date +%Y%m%d_%H%M%S).tar.gz"

echo "Backup would be named: $backup_name"

Note: While both $() and backticks work for command substitution, $() is preferred because it's more readable and can be nested more easily.

Variables and Data Types

Shell scripting treats everything as strings by default, but understanding how to work with different types of data is crucial for creating robust scripts.

String Variables and Manipulation

#!/bin/bash

# String manipulation examples

 

# Basic string assignment

first_name="John"

last_name="Doe"

full_name="$first_name $last_name"

 

echo "Full name: $full_name"

 

# String length

echo "Length of full name: ${#full_name}"

 

# Substring extraction

echo "First 4 characters: ${full_name:0:4}"

echo "Last name only: ${full_name:5}"

 

# String replacement

message="Hello World"

new_message=${message/World/Universe}

echo "Original: $message"

echo "Modified: $new_message"

 

# Case conversion (Bash 4.0+)

uppercase_name=${full_name^^}

lowercase_name=${full_name,,}

echo "Uppercase: $uppercase_name"

echo "Lowercase: $lowercase_name"

Numeric Operations

#!/bin/bash

# Numeric operations in shell scripting

 

# Basic arithmetic using $(( ))

num1=10

num2=5

 

sum=$((num1 + num2))

difference=$((num1 - num2))

product=$((num1 * num2))

quotient=$((num1 / num2))

remainder=$((num1 % num2))

 

echo "Numbers: $num1 and $num2"

echo "Sum: $sum"

echo "Difference: $difference"

echo "Product: $product"

echo "Quotient: $quotient"

echo "Remainder: $remainder"

 

# Increment and decrement

counter=0

echo "Initial counter: $counter"

 

((counter++))

echo "After increment: $counter"

 

((counter += 5))

echo "After adding 5: $counter"

 

# Using expr (alternative method)

result=$(expr 15 + 25)

echo "Using expr: 15 + 25 = $result"

Command Explanations:

- $(( )): Arithmetic expansion for integer calculations
- ((counter++)): Increment operator (increases value by 1)
- ((counter += 5)): Compound assignment (adds 5 to current value)
- expr: External command for arithmetic operations (older method)

Arrays: Managing Collections of Data

Arrays allow you to store multiple values in a single variable, making it easier to manage collections of related data.

#!/bin/bash

# Array demonstration script

 

# Declaring arrays

fruits=("apple" "banana" "cherry" "date")

numbers=(1 2 3 4 5)

 

# Accessing array elements

echo "First fruit: ${fruits[0]}"

echo "Second fruit: ${fruits[1]}"

echo "All fruits: ${fruits[@]}"

echo "Number of fruits: ${#fruits[@]}"

 

# Adding elements to array

fruits+=("elderberry")

echo "After adding elderberry: ${fruits[@]}"

 

# Looping through array

echo "All fruits in the basket:"

for fruit in "${fruits[@]}"; do

echo " - $fruit"

done

 

# Array of system information

system_info=(

"Hostname: $(hostname)"

"Kernel: $(uname -r)"

"Architecture: $(uname -m)"

"Shell: $SHELL"

)

 

echo ""

echo "System Information:"

for info in "${system_info[@]}"; do

echo " $info"

done

Array Command Explanations:

- fruits=("apple" "banana" "cherry"): Creates an array with initial values
- ${fruits[0]}: Accesses the first element (arrays are zero-indexed)
- ${fruits[@]}: Expands to all array elements
- ${#fruits[@]}: Returns the number of elements in the array
- fruits+=("elderberry"): Appends a new element to the array

Input and Output Operations

Interactive scripts that can receive user input and provide formatted output are essential for creating user-friendly automation tools.

Reading User Input

#!/bin/bash

# Interactive script demonstration

 

echo "Welcome to the Personal Information Collector"

echo "============================================="

 

# Simple input

echo -n "Enter your name: "

read name

 

# Input with prompt

read -p "Enter your age: " age

 

# Silent input (for passwords)

read -s -p "Enter a password: " password

echo "" # New line after silent input

 

# Input with timeout

read -t 10 -p "Enter your favorite color (10 seconds): " color

 

# Input with default value

read -p "Enter your city [Default: Unknown]: " city

city=${city:-Unknown}

 

# Display collected information

echo ""

echo "Information Summary:"

echo "==================="

echo "Name: $name"

echo "Age: $age"

echo "Password: [Hidden for security]"

echo "Favorite Color: ${color:-Not specified}"

echo "City: $city"

Advanced Input Validation

#!/bin/bash

# Input validation example

 

validate_number() {

local input=$1

if [[ $input =~ ^[0-9]+$ ]]; then

return 0 # Valid number

else

return 1 # Invalid number

fi

}

 

echo "Number Validation Demo"

echo "====================="

 

while true; do

read -p "Enter a positive number: " user_input

if validate_number "$user_input"; then

echo "Valid number entered: $user_input"

break

else

echo "Invalid input. Please enter a positive number."

fi

done

 

echo "Thank you for entering a valid number!"

Input Command Explanations:

- read variable: Reads user input into a variable
- read -p "prompt" variable: Displays a prompt before reading input
- read -s variable: Silent input (doesn't echo characters - useful for passwords)
- read -t seconds variable: Reads input with a timeout
- ${variable:-default}: Uses default value if variable is empty

File Operations and Text Processing

Shell scripts excel at file manipulation and text processing tasks. Let's explore some practical examples:

#!/bin/bash

# File operations demonstration

 

log_file="system_log.txt"

backup_dir="backups"

 

# Create backup directory if it doesn't exist

if [ ! -d "$backup_dir" ]; then

mkdir -p "$backup_dir"

echo "Created backup directory: $backup_dir"

fi

 

# Create a sample log file

cat > "$log_file" << EOF

2024-01-15 10:30:00 INFO System startup completed

2024-01-15 10:30:15 INFO User login: alice

2024-01-15 10:45:22 WARNING Disk usage at 85%

2024-01-15 11:00:05 ERROR Failed to connect to database

2024-01-15 11:00:30 INFO Database connection restored

2024-01-15 11:15:45 INFO User logout: alice

EOF

 

echo "Sample log file created: $log_file"

 

# File information

echo ""

echo "File Information:"

echo "=================="

echo "File size: $(stat -c%s "$log_file") bytes"

echo "Last modified: $(stat -c%y "$log_file")"

echo "Number of lines: $(wc -l < "$log_file")"

 

# Text processing examples

echo ""

echo "Log Analysis:"

echo "============="

echo "INFO messages: $(grep -c "INFO" "$log_file")"

echo "WARNING messages: $(grep -c "WARNING" "$log_file")"

echo "ERROR messages: $(grep -c "ERROR" "$log_file")"

 

# Extract errors to separate file

grep "ERROR" "$log_file" > "${backup_dir}/errors.log"

echo "Error messages extracted to: ${backup_dir}/errors.log"

 

# Create timestamped backup

timestamp=$(date +%Y%m%d_%H%M%S)

cp "$log_file" "${backup_dir}/system_log_${timestamp}.txt"

echo "Backup created: ${backup_dir}/system_log_${timestamp}.txt"

Best Practices and Script Organization

As your scripts grow in complexity, following best practices becomes crucial for maintainability and reliability:

Error Handling and Debugging

#!/bin/bash

# Best practices demonstration

 

# Enable strict error handling

set -euo pipefail

 

# Script metadata

SCRIPT_NAME=$(basename "$0")

SCRIPT_DIR=$(dirname "$0")

LOG_FILE="/tmp/${SCRIPT_NAME%.sh}.log"

 

# Logging function

log() {

local level=$1

shift

local message="$*"

local timestamp=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"

}

 

# Error handling function

error_exit() {

log "ERROR" "$1"

exit 1

}

 

# Cleanup function

cleanup() {

log "INFO" "Performing cleanup..."

# Add cleanup operations here

log "INFO" "Script execution completed"

}

 

# Set trap for cleanup on exit

trap cleanup EXIT

 

# Main script logic

main() {

log "INFO" "Starting $SCRIPT_NAME"

# Check if required command exists

if ! command -v curl &> /dev/null; then

error_exit "curl is required but not installed"

fi

# Example operation with error checking

if ! mkdir -p "/tmp/test_directory" 2>/dev/null; then

error_exit "Failed to create test directory"

fi

log "INFO" "Test directory created successfully"

# Simulate some work

sleep 2

log "INFO" "Main operations completed successfully"

}

 

# Run main function

main "$@"

Best Practice Command Explanations:

- set -euo pipefail: Enables strict error handling
- -e: Exit on any command failure
- -u: Exit on undefined variable usage
- -o pipefail: Exit on pipe failures
- trap cleanup EXIT: Runs cleanup function when script exits
- command -v curl: Checks if a command exists
- 2>/dev/null: Redirects error output to null (suppresses error messages)

Practical Script Examples

Let's conclude with some practical scripts that demonstrate real-world applications:

System Maintenance Script

#!/bin/bash

# Automated system maintenance script

 

echo "Starting System Maintenance..."

echo "=============================="

 

# Update package lists

echo "Updating package lists..."

if sudo apt update; then

echo "✓ Package lists updated successfully"

else

echo "✗ Failed to update package lists"

fi

 

# Clean package cache

echo "Cleaning package cache..."

if sudo apt autoclean; then

echo "✓ Package cache cleaned"

else

echo "✗ Failed to clean package cache"

fi

 

# Remove orphaned packages

echo "Removing orphaned packages..."

if sudo apt autoremove -y; then

echo "✓ Orphaned packages removed"

else

echo "✗ Failed to remove orphaned packages"

fi

 

# Clean temporary files

echo "Cleaning temporary files..."

temp_files_before=$(find /tmp -type f | wc -l)

sudo find /tmp -type f -atime +7 -delete 2>/dev/null

temp_files_after=$(find /tmp -type f | wc -l)

echo "✓ Cleaned $((temp_files_before - temp_files_after)) temporary files"

 

echo ""

echo "System maintenance completed!"

This chapter has taken you on a comprehensive journey through the fundamentals of shell scripting. You've learned to create variables, process user input, manipulate files, and implement best practices for robust script development. The power of shell scripting lies not just in automating individual tasks, but in creating intelligent solutions that can adapt, decide, and respond to different scenarios.

As you continue your shell scripting journey, remember that every expert was once a beginner. Start with simple scripts, gradually add complexity, and don't be afraid to experiment. The command line is your canvas, and shell scripting is your brush—use them to paint solutions that make your digital life more efficient and enjoyable.

The next time you find yourself repeating the same sequence of commands, pause and ask yourself: "Could this be a script?" More often than not, the answer will be yes, and you'll have another opportunity to practice and refine your shell scripting skills.