HxHippy

Shell Scripting Fundamentals

Write robust Bash scripts with proper error handling, variables, and control flow.

Last updated: 2024-12-18

Learn to write reliable, maintainable shell scripts. From basic syntax to advanced patterns.

Script Basics

The Shebang

#!/bin/bash
# This tells the system to use bash

#!/usr/bin/env bash
# More portable - finds bash in PATH

Running Scripts

# Make executable and run
chmod +x script.sh
./script.sh

# Run with bash directly
bash script.sh

# Run in current shell (for sourcing)
source script.sh
# or
. script.sh

Variables

Basic Variables

# Assignment (no spaces around =)
name="John"
count=42

# Using variables
echo "Hello, $name"
echo "Count: ${count}"  # Braces recommended

# Command substitution
today=$(date +%Y-%m-%d)
files=$(ls *.txt)

# Arithmetic
result=$((5 + 3))
((count++))

Special Variables

$0       # Script name
$1-$9   # Positional parameters
$#       # Number of arguments
$@       # All arguments (as separate words)
$*       # All arguments (as single word)
$?       # Exit status of last command
$$       # Current process ID
$!       # PID of last background process

Variable Operations

# Default values
${var:-default}    # Use default if var unset
${var:=default}    # Set and use default if var unset
${var:?error}      # Exit with error if var unset

# String operations
${#var}            # Length
${var:0:5}         # Substring (first 5 chars)
${var#pattern}     # Remove shortest prefix match
${var##pattern}    # Remove longest prefix match
${var%pattern}     # Remove shortest suffix match
${var%%pattern}    # Remove longest suffix match
${var/old/new}     # Replace first match
${var//old/new}    # Replace all matches

Arrays

# Indexed arrays
fruits=("apple" "banana" "cherry")
echo "${fruits[0]}"      # First element
echo "${fruits[@]}"      # All elements
echo "${#fruits[@]}"     # Array length
fruits+=("date")         # Append

# Associative arrays (bash 4+)
declare -A colors
colors[red]="#FF0000"
colors[green]="#00FF00"
echo "${colors[red]}"
echo "${!colors[@]}"     # All keys

Control Flow

Conditionals

# if statement
if [ "$count" -gt 10 ]; then
    echo "Greater than 10"
elif [ "$count" -eq 10 ]; then
    echo "Equals 10"
else
    echo "Less than 10"
fi

# Modern test syntax
if [[ "$name" == "John" ]]; then
    echo "Hello John"
fi

# File tests
if [[ -f "$file" ]]; then
    echo "File exists"
fi

if [[ -d "$dir" ]]; then
    echo "Directory exists"
fi

Test Operators

# String comparisons (use [[ ]] for these)
[[ "$a" == "$b" ]]     # Equal
[[ "$a" != "$b" ]]     # Not equal
[[ -z "$a" ]]          # Empty
[[ -n "$a" ]]          # Not empty

# Numeric comparisons (use [ ] or (( )))
[ "$a" -eq "$b" ]      # Equal
[ "$a" -ne "$b" ]      # Not equal
[ "$a" -lt "$b" ]      # Less than
[ "$a" -le "$b" ]      # Less than or equal
[ "$a" -gt "$b" ]      # Greater than
[ "$a" -ge "$b" ]      # Greater than or equal

# File tests
[ -f "$f" ]    # Regular file exists
[ -d "$d" ]    # Directory exists
[ -r "$f" ]    # Readable
[ -w "$f" ]    # Writable
[ -x "$f" ]    # Executable
[ -s "$f" ]    # File not empty

# Logical operators
[[ cond1 && cond2 ]]  # AND
[[ cond1 || cond2 ]]  # OR
[[ ! cond ]]          # NOT

Loops

# For loop
for item in apple banana cherry; do
    echo "$item"
done

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

# Loop over files
for file in *.txt; do
    echo "Processing $file"
done

# Loop over array
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

# While loop
while [ "$count" -lt 10 ]; do
    echo "$count"
    ((count++))
done

# Read file line by line
while IFS= read -r line; do
    echo "$line"
done < file.txt

# Infinite loop
while true; do
    # ...
    sleep 1
done

Case Statement

case "$1" in
    start)
        echo "Starting..."
        ;;
    stop)
        echo "Stopping..."
        ;;
    restart)
        echo "Restarting..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac

Functions

# Define function
greet() {
    local name="$1"  # Local variable
    echo "Hello, $name"
}

# Call function
greet "World"

# Return values (exit status)
is_valid() {
    if [[ "$1" =~ ^[0-9]+$ ]]; then
        return 0  # Success
    else
        return 1  # Failure
    fi
}

if is_valid "123"; then
    echo "Valid number"
fi

# Return string (via stdout)
get_date() {
    date +%Y-%m-%d
}
today=$(get_date)

Error Handling

Exit Status and set Options

#!/bin/bash
set -e          # Exit on error
set -u          # Exit on undefined variable
set -o pipefail # Exit on pipe failure
# Combined:
set -euo pipefail

# Check command success
if ! command; then
    echo "Command failed"
    exit 1
fi

# Trap errors
trap 'echo "Error on line $LINENO"' ERR

# Cleanup on exit
cleanup() {
    rm -f /tmp/myfile.tmp
}
trap cleanup EXIT

Input Validation

#!/bin/bash
set -euo pipefail

# Require arguments
if [ "$#" -lt 1 ]; then
    echo "Usage: $0 <filename>"
    exit 1
fi

# Validate file exists
if [ ! -f "$1" ]; then
    echo "Error: File '$1' not found"
    exit 1
fi

Best Practices

Script Template

#!/usr/bin/env bash
#
# Description: Brief description of script
# Usage: script.sh [options] <arguments>
#

set -euo pipefail

# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"

# Default values
VERBOSE=false

# Functions
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

error() {
    log "ERROR: $*" >&2
    exit 1
}

usage() {
    cat <<EOF
Usage: $SCRIPT_NAME [options] <argument>

Options:
    -v, --verbose    Enable verbose output
    -h, --help       Show this help message

Arguments:
    argument         Description of argument
EOF
}

# Parse arguments
while [[ "$#" -gt 0 ]]; do
    case "$1" in
        -v|--verbose)
            VERBOSE=true
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            break
            ;;
    esac
done

# Validate arguments
[[ "$#" -lt 1 ]] && { usage; exit 1; }

# Main logic
main() {
    log "Starting..."
    # Your code here
    log "Done."
}

main "$@"
beginner Shell Scripting 45 min read

Related Tutorials

bashshell scriptingscriptsautomationbash variablesbash functions