Knowledge Overview

Prerequisites

basic terminale knowledge

Time Investment

20 minutes reading time
40-60 minutes hands-on practice

Guide Content

What is Bash Scripting?

Bash scripting for beginners is the process of writing executable text files containing sequential shell commands to automate repetitive tasks in Linux systems. A bash script starts with #!/bin/bash (shebang), contains commands you'd normally type in the terminal, and can be executed like any program. For example, create a file with .sh extension, write your commands, make it executable with chmod +x script.sh, and run it with ./script.sh.

Quick Start Example:

Bash
#!/bin/bash
# My First Bash Script
echo "Hello, World!"
echo "Today is $(date)"

Table of Contents

  1. What is Bash Scripting and Why Learn It?
  2. How to Create Your First Bash Script
  3. Understanding Bash Script Variables
  4. How to Use Conditional Statements in Bash
  5. Bash Script Loops Tutorial
  6. Working with Functions in Bash Scripts
  7. Best Practices for Shell Scripting
  8. Troubleshooting Common Bash Script Errors
  9. FAQ: Bash Scripting for Beginners

What is Bash Scripting and Why Learn It?

Bash (Bourne Again Shell) scripting is a fundamental skill for Linux system administrators, developers, and DevOps engineers. Moreover, bash scripting for beginners provides an accessible entry point into automation and system administration tasks.

Why Bash Scripts Matter

Bash scripts enable you to:

  • Automate repetitive tasks like backups, log rotation, and system updates
  • Chain multiple commands together with error handling
  • Schedule tasks using cron jobs for automated execution
  • Standardize procedures across multiple servers
  • Save time by eliminating manual command execution

Furthermore, bash scripting serves as a gateway to understanding Linux system internals, process management, and file operations. Therefore, mastering bash scripts enhances your overall Linux proficiency.

Real-World Use Cases:


How to Create Your First Bash Script

Creating your first bash script involves several straightforward steps. Additionally, understanding the basic structure ensures your scripts run correctly across different Linux distributions.

1: Choose Your Text Editor

You can use any text editor to write bash scripts:

Bash
# Using nano (beginner-friendly)
nano myscript.sh

# Using vim (advanced)
vim myscript.sh

# Using VS Code
code myscript.sh

2: Write the Shebang Line

The shebang (#!) tells the system which interpreter to use. Consequently, this line must be the first line in your script:

Bash
#!/bin/bash

Alternative shebangs:

Bash
#!/usr/bin/env bash  # More portable across systems
#!/bin/sh            # POSIX-compliant shell

3: Add Your Commands

Here's a complete beginner script example:

Bash
#!/bin/bash

# Script: hello_world.sh
# Description: My first bash script
# Author: Your Name
# Date: 2025-01-07

echo "================================"
echo "  Welcome to Bash Scripting!"
echo "================================"
echo ""
echo "Current user: $USER"
echo "Home directory: $HOME"
echo "Current directory: $(pwd)"
echo "Today's date: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
echo "System information:"
uname -a

4: Make the Script Executable

Scripts need execute permissions to run. Therefore, use the chmod command:

Bash
chmod +x hello_world.sh

Understanding permissions:

Bash
# View current permissions
ls -l hello_world.sh

# Output: -rwxr-xr-x 1 user group 245 Jan 07 10:30 hello_world.sh
#         ↑↑↑↑↑↑↑↑↑
#         ||||||||| 
#         Owner: rwx (read, write, execute)
#         Group: r-x (read, execute)
#         Others: r-x (read, execute)

5: Execute Your Script

Run your script using one of these methods:

Bash
# Method 1: Direct execution (requires execute permission)
./hello_world.sh

# Method 2: Explicit bash interpreter
bash hello_world.sh

# Method 3: Using sh (POSIX compatible)
sh hello_world.sh

# Method 4: Source the script (runs in current shell)
source hello_world.sh
# or
. hello_world.sh

Expected Output:

Bash
================================
  Welcome to Bash Scripting!
================================

Current user: john
Home directory: /home/john
Current directory: /home/john/scripts
Today's date: 2025-01-07 10:35:42

System information:
Linux hostname 5.15.0-91-generic #101-Ubuntu SMP x86_64 GNU/Linux

Understanding Bash Script Variables

Variables are fundamental to bash scripting for beginners. Moreover, they store data that can be referenced and manipulated throughout your script.

How to Declare Variables in Bash

Variables in bash are declared without type keywords. Additionally, there should be no spaces around the equals sign:

Bash
#!/bin/bash

# String variables
name="John Doe"
email="john@example.com"

# Integer variables
age=30
count=100

# Command substitution
current_date=$(date)
user_home=$HOME

# Array variables
fruits=("apple" "banana" "cherry")

echo "Name: $name"
echo "Email: $email"
echo "Age: $age"
echo "Date: $current_date"
echo "First fruit: ${fruits[0]}"

Variable Naming Conventions

Follow these best practices when naming variables:

RuleExampleWhy
Use descriptive namesbackup_directory not bdImproves readability
Use lowercase for localtemp_fileConvention standard
Use uppercase for constantsMAX_RETRY=3Indicates immutability
Use underscoreslog_file_pathSeparates words clearly
Avoid special charactersuser_name not user-namePrevents syntax errors

Special Variables in Bash

Bash provides several built-in special variables. Furthermore, these variables offer valuable information about the script's execution context:

Bash
#!/bin/bash

echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
echo "Process ID: $$"
echo "Last command exit status: $?"
echo "Current user: $USER"
echo "Home directory: $HOME"
echo "Current working directory: $PWD"

Testing special variables:

Bash
# Save as test_vars.sh
./test_vars.sh arg1 arg2 arg3

# Output:
# Script name: ./test_vars.sh
# First argument: arg1
# Second argument: arg2
# All arguments: arg1 arg2 arg3
# Number of arguments: 3
# Process ID: 12345

Variable Expansion and Quoting

Understanding quoting prevents common errors. Consequently, proper quoting ensures variables expand correctly:

Bash
#!/bin/bash

text="Hello World"

# Unquoted (word splitting occurs)
echo $text            # Output: Hello World

# Double quotes (preserves whitespace, allows variable expansion)
echo "$text"          # Output: Hello World

# Single quotes (treats everything as literal)
echo '$text'          # Output: $text

# No quotes with special characters (WRONG)
file=my file.txt      # ERROR: unexpected syntax

# Correct way
file="my file.txt"    # Correct
file='my file.txt'    # Also correct

Advanced variable manipulation:

Bash
#!/bin/bash

# String length
name="Linux"
echo "Length: ${#name}"  # Output: 5

# Substring
echo "First 3 chars: ${name:0:3}"  # Output: Lin

# Default values
echo "Value: ${undefined_var:-default}"  # Output: default

# Variable replacement
path="/home/user/documents/file.txt"
echo "Filename: ${path##*/}"      # Output: file.txt
echo "Directory: ${path%/*}"       # Output: /home/user/documents

How to Use Conditional Statements in Bash

Conditional statements enable bash scripting for beginners to make decisions. Additionally, these constructs allow scripts to respond differently based on varying conditions.

Basic If-Then-Else Structure

The fundamental conditional structure in bash follows this syntax:

Bash
#!/bin/bash

# Simple if statement
age=25

if [ $age -ge 18 ]; then
    echo "You are an adult"
fi

# If-else statement
if [ $age -lt 18 ]; then
    echo "You are a minor"
else
    echo "You are an adult"
fi

# If-elif-else statement
if [ $age -lt 13 ]; then
    echo "You are a child"
elif [ $age -lt 18 ]; then
    echo "You are a teenager"
else
    echo "You are an adult"
fi

Comparison Operators Reference

Understanding comparison operators is essential. Therefore, here's a comprehensive reference table:

Numeric Comparisons:

OperatorDescriptionExample
-eqEqual to[ $a -eq $b ]
-neNot equal to[ $a -ne $b ]
-gtGreater than[ $a -gt $b ]
-geGreater than or equal[ $a -ge $b ]
-ltLess than[ $a -lt $b ]
-leLess than or equal[ $a -le $b ]

String Comparisons:

OperatorDescriptionExample
= or ==Equal to[ "$a" = "$b" ]
!=Not equal to[ "$a" != "$b" ]
<Less than (lexicographic)[[ "$a" < "$b" ]]
>Greater than (lexicographic)[[ "$a" > "$b" ]]
-zString is empty[ -z "$string" ]
-nString is not empty[ -n "$string" ]

File Test Operators:

OperatorDescriptionExample
-eFile exists[ -e /path/file ]
-fRegular file exists[ -f /path/file ]
-dDirectory exists[ -d /path/dir ]
-rFile is readable[ -r /path/file ]
-wFile is writable[ -w /path/file ]
-xFile is executable[ -x /path/file ]
-sFile is not empty[ -s /path/file ]

Practical Conditional Examples

Here's a real-world script demonstrating conditionals:

Bash
#!/bin/bash

# Script: check_system.sh
# Description: System health checker

LOG_FILE="/var/log/syslog"
DISK_THRESHOLD=80

echo "=== System Health Check ==="
echo ""

# Check if log file exists
if [ -f "$LOG_FILE" ]; then
    echo "βœ“ Log file exists: $LOG_FILE"
    
    # Check if log file is readable
    if [ -r "$LOG_FILE" ]; then
        echo "βœ“ Log file is readable"
    else
        echo "βœ— Log file is not readable"
        exit 1
    fi
else
    echo "βœ— Log file not found: $LOG_FILE"
    exit 1
fi

# Check disk usage
disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')

if [ $disk_usage -gt $DISK_THRESHOLD ]; then
    echo "βœ— WARNING: Disk usage is ${disk_usage}% (threshold: ${DISK_THRESHOLD}%)"
    exit 1
else
    echo "βœ“ Disk usage is healthy: ${disk_usage}%"
fi

# Check if running as root
if [ "$EUID" -eq 0 ]; then
    echo "βœ“ Running with root privileges"
else
    echo "⚠ Running without root privileges"
fi

echo ""
echo "System check completed successfully!"

Using Double Brackets [[ ]]

Modern bash supports double brackets, which offer enhanced features. Moreover, double brackets provide better string handling:

Bash
#!/bin/bash

string="Hello World"

# Pattern matching (only works with [[]])
if [[ $string == Hello* ]]; then
    echo "String starts with 'Hello'"
fi

# Regular expression matching
if [[ $string =~ ^[A-Z] ]]; then
    echo "String starts with uppercase letter"
fi

# Logical operators with double brackets
age=25
name="John"

if [[ $age -gt 18 && $name == "John" ]]; then
    echo "Adult named John"
fi

# No word splitting issues
if [[ -f $HOME/.bashrc ]]; then
    echo "Bashrc exists"
fi

Bash Script Loops Tutorial

Loops enable automation of repetitive tasks. Furthermore, bash scripting for beginners becomes powerful when combined with loop constructs.

For Loop: Iterate Over Lists

The for loop iterates over a list of items. Additionally, it's perfect for processing files, arrays, or sequences:

Bash
#!/bin/bash

# Loop through a list of values
echo "Fruits:"
for fruit in apple banana cherry grape; do
    echo "  - $fruit"
done

# Loop through files in directory
echo ""
echo "Files in current directory:"
for file in *.txt; do
    if [ -f "$file" ]; then
        echo "  Processing: $file"
    fi
done

# Loop through a range of numbers
echo ""
echo "Counting 1 to 5:"
for i in {1..5}; do
    echo "  Count: $i"
done

# C-style for loop
echo ""
echo "Even numbers 0 to 10:"
for ((i=0; i<=10; i+=2)); do
    echo "  $i"
done

While Loop: Condition-Based Iteration

The while loop continues until a condition becomes false. Therefore, it's ideal for monitoring or waiting:

Bash
#!/bin/bash

# Basic while loop
counter=1
while [ $counter -le 5 ]; do
    echo "Iteration: $counter"
    counter=$((counter + 1))
done

# Reading file line by line
echo ""
echo "Reading /etc/passwd (first 5 lines):"
line_count=0
while IFS=: read -r username password uid gid comment home shell; do
    echo "User: $username, UID: $uid, Home: $home"
    line_count=$((line_count + 1))
    [ $line_count -ge 5 ] && break
done < /etc/passwd

# Infinite loop with break condition
echo ""
echo "Monitoring process (Ctrl+C to stop):"
while true; do
    load=$(uptime | awk '{print $(NF-2)}' | sed 's/,//')
    echo "Current load: $load"
    
    # Break if load exceeds threshold
    if (( $(echo "$load > 2.0" | bc -l) )); then
        echo "Load too high! Exiting..."
        break
    fi
    
    sleep 5
done

Until Loop: Inverse While Loop

The until loop runs until a condition becomes true. Consequently, it's useful for waiting operations:

Bash
#!/bin/bash

# Wait for file to appear
target_file="/tmp/ready.flag"
counter=0

echo "Waiting for $target_file to appear..."
until [ -f "$target_file" ] || [ $counter -gt 10 ]; do
    echo "  Attempt $counter: File not found"
    sleep 2
    counter=$((counter + 1))
done

if [ -f "$target_file" ]; then
    echo "File found!"
else
    echo "Timeout: File did not appear"
fi

Loop Control: Break and Continue

Control loop execution flow with break and continue:

Bash
#!/bin/bash

# Using continue to skip iterations
echo "Odd numbers from 1 to 10:"
for i in {1..10}; do
    # Skip even numbers
    if [ $((i % 2)) -eq 0 ]; then
        continue
    fi
    echo "  $i"
done

# Using break to exit loop early
echo ""
echo "Finding first .log file:"
for file in /var/log/*; do
    if [[ $file == *.log ]]; then
        echo "Found: $file"
        break
    fi
done

Working with Functions in Bash Scripts

Functions modularize code and improve reusability. Moreover, they make bash scripting for beginners more organized and maintainable.

How to Define Functions in Bash

Functions can be defined using two syntax styles:

Bash
#!/bin/bash

# Method 1: Using 'function' keyword
function greet() {
    echo "Hello, $1!"
}

# Method 2: Without 'function' keyword (more common)
say_goodbye() {
    echo "Goodbye, $1!"
}

# Calling functions
greet "World"
say_goodbye "Friend"

Functions with Parameters

Functions accept parameters just like scripts. Furthermore, they can return values using exit codes:

Bash
#!/bin/bash

# Function with multiple parameters
create_backup() {
    local source_dir=$1
    local backup_dir=$2
    local timestamp=$(date +%Y%m%d_%H%M%S)
    
    if [ -d "$source_dir" ]; then
        echo "Creating backup of $source_dir..."
        tar -czf "${backup_dir}/backup_${timestamp}.tar.gz" "$source_dir"
        
        if [ $? -eq 0 ]; then
            echo "Backup created successfully"
            return 0
        else
            echo "Backup failed"
            return 1
        fi
    else
        echo "Error: Source directory does not exist"
        return 2
    fi
}

# Function that returns a value via echo
get_disk_usage() {
    local path=${1:-.}
    du -sh "$path" | awk '{print $1}'
}

# Using the functions
create_backup "/home/user/documents" "/backup"
echo "Exit code: $?"

echo ""
echo "Disk usage of /var: $(get_disk_usage /var)"

Local vs Global Variables

Variable scope affects how variables are accessed. Therefore, use local keyword for function-specific variables:

Bash
#!/bin/bash

# Global variable
global_var="I am global"

demo_scope() {
    local local_var="I am local"
    global_var="Modified global"
    
    echo "Inside function:"
    echo "  Local: $local_var"
    echo "  Global: $global_var"
}

echo "Before function:"
echo "  Global: $global_var"

demo_scope

echo ""
echo "After function:"
echo "  Global: $global_var"
# echo "  Local: $local_var"  # This would be empty

Practical Function Library Example

Create reusable function libraries:

Bash
#!/bin/bash

# File: logger_lib.sh
# Description: Logging utility functions

LOG_FILE="/var/log/myscript.log"
LOG_LEVEL="INFO"

log_message() {
    local level=$1
    shift
    local message="$@"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

log_info() {
    log_message "INFO" "$@"
}

log_warning() {
    log_message "WARNING" "$@"
}

log_error() {
    log_message "ERROR" "$@"
}

check_root() {
    if [ "$EUID" -ne 0 ]; then
        log_error "This script must be run as root"
        exit 1
    fi
}

# Usage example
log_info "Script started"
check_root
log_warning "Disk space running low"
log_error "Failed to connect to database"

Best Practices for Shell Scripting

Following best practices ensures your scripts are reliable and maintainable. Additionally, these guidelines prevent common pitfalls in bash scripting for beginners.

Script Header and Documentation

Always include comprehensive headers. Moreover, document your script's purpose and usage:

Bash
#!/bin/bash

################################################################################
# Script Name: system_backup.sh
# Description: Automated system backup with rotation
# Author: John Doe
# Email: john@example.com
# Date Created: 2025-01-07
# Date Modified: 2025-01-07
# Version: 1.0.0
#
# Usage: ./system_backup.sh [OPTIONS]
#   Options:
#     -d, --directory DIR    Backup directory (default: /backup)
#     -r, --retention DAYS   Retention period (default: 7)
#     -h, --help            Display this help message
#
# Dependencies: tar, gzip, date
# Exit Codes:
#   0 - Success
#   1 - General error
#   2 - Missing dependencies
################################################################################

# Enable strict error handling
set -euo pipefail

# Script configuration
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(dirname "$0")
readonly VERSION="1.0.0"

Error Handling and Validation

Implement robust error handling. Consequently, your scripts fail gracefully:

Bash
#!/bin/bash

set -euo pipefail  # Exit on error, undefined variables, pipe failures

# Error trap
trap 'error_handler $? $LINENO' ERR

error_handler() {
    local exit_code=$1
    local line_number=$2
    echo "Error: Script failed at line $line_number with exit code $exit_code" >&2
    exit "$exit_code"
}

# Input validation
validate_directory() {
    local dir=$1
    
    if [ -z "$dir" ]; then
        echo "Error: Directory path is empty" >&2
        return 1
    fi
    
    if [ ! -d "$dir" ]; then
        echo "Error: Directory does not exist: $dir" >&2
        return 1
    fi
    
    if [ ! -r "$dir" ]; then
        echo "Error: Directory is not readable: $dir" >&2
        return 1
    fi
    
    return 0
}

# Usage
if validate_directory "/var/log"; then
    echo "Directory is valid"
fi

Command-Line Argument Parsing

Parse arguments professionally using getopts:

Bash
#!/bin/bash

# Default values
VERBOSE=false
OUTPUT_FILE=""
CONFIG_FILE="/etc/myapp.conf"

# Usage function
usage() {
    cat << EOF
Usage: $0 [OPTIONS]

Options:
    -v, --verbose          Enable verbose output
    -o, --output FILE      Output file path
    -c, --config FILE      Configuration file (default: $CONFIG_FILE)
    -h, --help            Display this help message

Examples:
    $0 -v -o result.txt
    $0 --config custom.conf --output data.out
EOF
    exit 0
}

# Parse arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        -v|--verbose)
            VERBOSE=true
            shift
            ;;
        -o|--output)
            OUTPUT_FILE="$2"
            shift 2
            ;;
        -c|--config)
            CONFIG_FILE="$2"
            shift 2
            ;;
        -h|--help)
            usage
            ;;
        *)
            echo "Error: Unknown option: $1" >&2
            echo "Use -h or --help for usage information"
            exit 1
            ;;
    esac
done

# Validate required arguments
if [ -z "$OUTPUT_FILE" ]; then
    echo "Error: Output file is required (-o option)" >&2
    exit 1
fi

# Use the arguments
if [ "$VERBOSE" = true ]; then
    echo "Verbose mode enabled"
    echo "Output file: $OUTPUT_FILE"
    echo "Config file: $CONFIG_FILE"
fi

Security Considerations

Protect your scripts from common security issues:

Bash
#!/bin/bash

# 1. Always quote variables to prevent word splitting
file_path="/path/to/my file.txt"
if [ -f "$file_path" ]; then  # GOOD
    echo "File exists"
fi

# 2. Use absolute paths for commands
readonly RM_CMD="/bin/rm"
readonly TAR_CMD="/bin/tar"

$RM_CMD -f /tmp/tempfile
$TAR_CMD -czf backup.tar.gz /data

# 3. Sanitize user input
sanitize_input() {
    local input=$1
    # Remove potentially dangerous characters
    echo "$input" | tr -cd '[:alnum:]._-'
}

user_input="file; rm -rf /"
safe_input=$(sanitize_input "$user_input")
echo "Sanitized: $safe_input"

# 4. Use readonly for constants
readonly MAX_RETRIES=3
readonly TIMEOUT=30

# 5. Avoid eval - use arrays instead
# BAD: eval "$dangerous_command"

# GOOD:
declare -a safe_commands=("ls" "-la" "/tmp")
"${safe_commands[@]}"

Troubleshooting Common Bash Script Errors

Even experienced scripters encounter errors. Therefore, understanding common issues helps debug scripts efficiently.

"command not found"

Symptom:

Bash
./script.sh: line 5: mycommand: command not found

Causes and Solutions:

  1. Missing executable in PATH:
Bash
# Check if command exists
which mycommand

# Use full path
/usr/local/bin/mycommand

# Or add to PATH
export PATH=$PATH:/usr/local/bin
  1. Typo in command name:
Bash
# Check spelling
pytohn script.py  # WRONG
python script.py  # CORRECT
  1. Missing shebang or wrong interpreter:
Bash
#!/bin/bash  # Ensure this is first line

"Permission denied"

Symptom:

Bash
bash: ./script.sh: Permission denied

Solution:

Bash
# Make script executable
chmod +x script.sh

# Verify permissions
ls -l script.sh

# Alternative: run with bash explicitly
bash script.sh

"Syntax error near unexpected token"

Symptom:

Bash
./script.sh: line 10: syntax error near unexpected token `fi'

Common Causes:

  1. Missing keywords:
Bash
# WRONG
if [ $x -eq 1 ]; then
    echo "x is 1"
# Missing 'fi'

# CORRECT
if [ $x -eq 1 ]; then
    echo "x is 1"
fi
  1. Incorrect spacing in conditionals:
Bash
# WRONG
if [$x -eq 1]; then  # No spaces inside brackets

# CORRECT
if [ $x -eq 1 ]; then  # Spaces required
  1. Unmatched quotes:
Bash
# WRONG
echo "Hello World  # Missing closing quote

# CORRECT
echo "Hello World"

"Unbound variable"

Symptom (when using set -u):

Bash
./script.sh: line 15: my_var: unbound variable

Solutions:

Bash
# Set default value
my_var=${my_var:-"default"}

# Check if variable is set
if [ -n "${my_var:-}" ]; then
    echo "$my_var"
fi

# Use conditional assignment
: "${my_var:=default_value}"

Debugging Techniques

Enable debugging modes to trace execution:

Bash
#!/bin/bash

# Enable debug mode (print each command before execution)
set -x

# Enable verbose mode (print input lines)
set -v

# Disable debug mode
set +x

# Conditional debugging
DEBUG=${DEBUG:-false}
if [ "$DEBUG" = true ]; then
    set -x
fi

# Debug specific section
echo "Starting critical section"
set -x
critical_command_1
critical_command_2
set +x
echo "Critical section completed"

Running scripts with debugging:

Bash
# Full script debugging
bash -x script.sh

# Debug with verbose output
bash -xv script.sh

# Check syntax without execution
bash -n script.sh

Common Logic Errors

File test mistakes:

Bash
# WRONG - Missing quotes
if [ -f $file ]; then  # Fails if $file contains spaces

# CORRECT
if [ -f "$file" ]; then

# WRONG - Using -e for files
if [ -e "$file" ]; then  # Matches directories too

# CORRECT - Use -f for regular files
if [ -f "$file" ]; then

Arithmetic comparison errors:

Bash
# WRONG - Using string comparison for numbers
if [ $num > 10 ]; then  # String comparison, creates file "10"

# CORRECT - Use numeric comparison
if [ $num -gt 10 ]; then

Diagnostic Commands

Use these commands to diagnose issues:

Bash
# Check script syntax
bash -n script.sh

# View environment variables
printenv
env

# Trace system calls
strace -e trace=open,read,write ./script.sh

# Check which shell is being used
echo $SHELL
echo $0

# View bash version
bash --version

# Check if commands exist
command -v python
type python
which python

FAQ: Bash Scripting for Beginners

How do I start learning bash scripting?

Start with basic commands in the terminal, then create simple scripts combining those commands. Additionally, practice writing small automation scripts for tasks you do regularly. Resources like the GNU Bash Manual and Linux Documentation Project provide excellent learning materials.

What's the difference between sh and bash?

sh (Bourne Shell) is the original Unix shell with POSIX compliance. bash (Bourne Again Shell) is an enhanced version with additional features like arrays, extended pattern matching, and more built-in commands. Therefore, use #!/bin/bash for scripts requiring bash-specific features and #!/bin/sh for maximum portability.

How do I debug a bash script that isn't working?

Enable debugging with set -x at the start of your script or run it with bash -x script.sh. Furthermore, add echo statements to print variable values. Check syntax with bash -n script.sh before execution. Use shellcheck (available at ShellCheck.net) for static analysis.

Should I use single or double quotes in bash scripts?

Use double quotes when you need variable expansion: echo "$HOME". Use single quotes for literal strings where no expansion occurs: echo 'Cost: $100'. Additionally, always quote variables to prevent word splitting: [ -f "$file" ] not [ -f $file ].

How can I pass arguments to a bash script?

Access arguments using positional parameters: $1, $2, etc. The special variable $@ contains all arguments, and $# contains the count. Example:

Bash
#!/bin/bash
echo "First arg: $1"
echo "All args: $@"
echo "Number of args: $#"

What exit codes should my bash script return?

Return 0 for success and non-zero for errors. Conventionally: 1 for general errors, 2 for misuse of shell commands, 126 for command cannot execute, 127 for command not found. Moreover, define custom exit codes in your script header for specific error conditions.

How do I make my bash script run automatically?

Schedule scripts with cron for recurring tasks. Add entries to crontab with crontab -e. For system boot execution, use systemd services or add scripts to /etc/rc.local. Alternatively, use systemd timers for more complex scheduling needs. See systemd.io for modern scheduling methods.

Can bash scripts work on all Linux distributions?

Scripts using standard POSIX commands work across distributions. However, distribution-specific commands (like package managers: apt vs yum) require conditional logic. Furthermore, use #!/bin/sh for maximum portability or check the distribution with /etc/os-release.

How do I handle errors in bash scripts?

Use set -e to exit on errors, set -u for undefined variables, and set -o pipefail for pipe failures. Implement trap handlers for cleanup:

Bash
set -euo pipefail
trap 'echo "Error at line $LINENO"' ERR

Additionally, check exit codes with $? after critical commands.

What are the best resources for learning bash scripting?

Authoritative resources:


Real-World Bash Scripting Example

Here's a complete, production-ready script demonstrating best practices:

Bash
#!/bin/bash

################################################################################
# Script: system_monitor.sh
# Description: Monitor system resources and send alerts
# Author: LinuxTips.pro
# Version: 1.0.0
################################################################################

set -euo pipefail

# Configuration
readonly DISK_THRESHOLD=85
readonly MEMORY_THRESHOLD=90
readonly CPU_THRESHOLD=80
readonly LOG_FILE="/var/log/system_monitor.log"
readonly ADMIN_EMAIL="admin@example.com"

# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color

# Logging function
log() {
    local level=$1
    shift
    local message="$@"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE"
}

# Check disk usage
check_disk() {
    log "INFO" "Checking disk usage..."
    
    while read -r line; do
        usage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
        partition=$(echo "$line" | awk '{print $6}')
        
        if [ "$usage" -ge "$DISK_THRESHOLD" ]; then
            echo -e "${RED}βœ—${NC} Disk usage critical on $partition: ${usage}%"
            log "ERROR" "Disk usage critical on $partition: ${usage}%"
            return 1
        else
            echo -e "${GREEN}βœ“${NC} Disk usage healthy on $partition: ${usage}%"
        fi
    done < <(df -h | grep '^/dev/')
    
    return 0
}

# Check memory usage
check_memory() {
    log "INFO" "Checking memory usage..."
    
    local mem_usage=$(free | grep Mem | awk '{printf("%.0f", $3/$2 * 100)}')
    
    if [ "$mem_usage" -ge "$MEMORY_THRESHOLD" ]; then
        echo -e "${RED}βœ—${NC} Memory usage critical: ${mem_usage}%"
        log "ERROR" "Memory usage critical: ${mem_usage}%"
        return 1
    else
        echo -e "${GREEN}βœ“${NC} Memory usage healthy: ${mem_usage}%"
    fi
    
    return 0
}

# Check CPU load
check_cpu() {
    log "INFO" "Checking CPU load..."
    
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
    local cpu_int=${cpu_usage%.*}
    
    if [ "$cpu_int" -ge "$CPU_THRESHOLD" ]; then
        echo -e "${RED}βœ—${NC} CPU usage critical: ${cpu_usage}%"
        log "ERROR" "CPU usage critical: ${cpu_usage}%"
        return 1
    else
        echo -e "${GREEN}βœ“${NC} CPU usage healthy: ${cpu_usage}%"
    fi
    
    return 0
}

# Main function
main() {
    echo "========================================="
    echo "    System Resource Monitor"
    echo "    $(date '+%Y-%m-%d %H:%M:%S')"
    echo "========================================="
    echo ""
    
    local exit_code=0
    
    check_disk || exit_code=1
    echo ""
    
    check_memory || exit_code=1
    echo ""
    
    check_cpu || exit_code=1
    echo ""
    
    if [ $exit_code -eq 0 ]; then
        echo -e "${GREEN}All checks passed!${NC}"
        log "INFO" "All system checks passed"
    else
        echo -e "${RED}Some checks failed!${NC}"
        log "ERROR" "System check failures detected"
    fi
    
    echo "========================================="
    
    return $exit_code
}

# Run main function
main "$@"
exit $?

Conclusion

Bash scripting for beginners opens the door to powerful Linux automation. Throughout this guide, we've covered essential concepts from basic script structure to advanced control flow. Furthermore, you've learned variable handling, conditional logic, loops, and functionsβ€”the building blocks of effective scripts.

Remember these key takeaways:

  • Always start scripts with proper shebangs (#!/bin/bash)
  • Quote variables to prevent word splitting errors
  • Implement error handling with set -euo pipefail
  • Document your code with clear comments and headers
  • Test scripts thoroughly before production use

Moreover, continue learning by reading official documentation, analyzing existing scripts, and practicing regularly. The Linux Foundation offers excellent resources for deepening your Linux expertise.

Next Steps:

  1. Practice writing scripts for your daily tasks
  2. Study the GNU Bash Manual
  3. Explore advanced topics like process management and signal handling
  4. Contribute to open-source projects to learn from experienced developers
  5. Join Linux communities on Stack Overflow and Reddit r/bash

Start automating todayβ€”your future self will thank you for the time saved!


Additional Resources

Official Documentation:

Learning Platforms:

Tools:

Community:


Author: LinuxTips.pro Editorial Team
Last Updated: January 7, 2025
Article Series: Linux Mastery 100 - Post #31
Reading Time: 25 minutes

Master Linux systematically with our comprehensive 100-topic series.