Skip to content

Linux Shell Scripting Guide

Shell scripting is essential for automating tasks in Linux. This guide covers bash scripting fundamentals, best practices, and practical examples for system administration.


Script Basics

Creating a Shell Script

#!/bin/bash
# This is a comment
# Shebang line tells system which interpreter to use

echo "Hello, World!"

Making Scripts Executable

# Create script
nano myscript.sh

# Make executable
chmod +x myscript.sh

# Run script
./myscript.sh

# Or
bash myscript.sh

Variables

Variable Declaration and Usage

#!/bin/bash

# Variable assignment (no spaces around =)
NAME="John"
AGE=25
TODAY=$(date +%Y-%m-%d)

# Using variables
echo "Name: $NAME"
echo "Age: ${AGE}"
echo "Today is $TODAY"

# Read-only variable
readonly PI=3.14159

# Unset variable
unset NAME

Special Variables

#!/bin/bash

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

Input and Output

Reading User Input

#!/bin/bash

# Basic input
echo "Enter your name:"
read name
echo "Hello, $name!"

# One-line prompt
read -p "Enter your age: " age

# Silent input (passwords)
read -sp "Enter password: " password
echo

# Read into array
read -a colors
echo "First color: ${colors[0]}"

# Read with timeout
read -t 5 -p "Quick! Enter something: " input

Command Line Arguments

#!/bin/bash

# Check number of arguments
if [ $# -eq 0 ]; then
    echo "Usage: $0 <filename>"
    exit 1
fi

# Use arguments
FILE=$1
OPTION=${2:-default}  # Default value if not provided

echo "Processing file: $FILE"
echo "Option: $OPTION"

Conditionals

If Statements

#!/bin/bash

# Basic if
if [ $AGE -gt 18 ]; then
    echo "Adult"
fi

# If-else
if [ $AGE -ge 18 ]; then
    echo "Adult"
else
    echo "Minor"
fi

# If-elif-else
if [ $AGE -lt 13 ]; then
    echo "Child"
elif [ $AGE -lt 18 ]; then
    echo "Teenager"
else
    echo "Adult"
fi

# Multiple conditions
if [ $AGE -gt 18 ] && [ "$NAME" != "" ]; then
    echo "Valid adult user"
fi

Test Operators

# Numeric comparisons
-eq    # Equal
-ne    # Not equal
-gt    # Greater than
-ge    # Greater than or equal
-lt    # Less than
-le    # Less than or equal

# String comparisons
=      # Equal
!=     # Not equal
-z     # Empty string
-n     # Not empty

# File tests
-e     # Exists
-f     # Is regular file
-d     # Is directory
-r     # Is readable
-w     # Is writable
-x     # Is executable
-s     # Not empty

Case Statements

#!/bin/bash

read -p "Enter fruit: " fruit

case $fruit in
    apple)
        echo "Red or green"
        ;;
    banana)
        echo "Yellow"
        ;;
    orange|lemon)
        echo "Citrus fruit"
        ;;
    *)
        echo "Unknown fruit"
        ;;
esac

Loops

For Loops

#!/bin/bash

# Iterate over list
for item in apple banana orange; do
    echo "Fruit: $item"
done

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

# Range
for i in {1..10}; do
    echo "Number:$i"
done

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

# Array iteration
fruits=(apple banana orange)
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

While Loops

#!/bin/bash

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

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

# Infinite loop
while true; do
    echo "Press Ctrl+C to stop"
    sleep 1
done

Until Loops

#!/bin/bash

count=0
until [ $count -ge 5 ]; do
    echo "Count: $count"
    ((count++))
done

Functions

Defining Functions

#!/bin/bash

# Method 1
function greet() {
    echo "Hello, $1!"
}

# Method 2
say_goodbye() {
    echo "Goodbye, $1!"
}

# Call functions
greet "John"
say_goodbye "Jane"

Function with Return Value

#!/bin/bash

add_numbers() {
    local num1=$1
    local num2=$2
    local sum=$((num1 + num2))
    echo $sum
}

result=$(add_numbers 5 3)
echo "Sum: $result"

# Return status code
check_file() {
    if [ -f "$1" ]; then
        return 0
    else
        return 1
    fi
}

if check_file "test.txt"; then
    echo "File exists"
else
    echo "File not found"
fi

Arrays

Array Operations

#!/bin/bash

# Declare array
fruits=("apple" "banana" "orange")

# Access elements
echo "${fruits[0]}"    # First element
echo "${fruits[@]}"    # All elements
echo "${#fruits[@]}"   # Array length

# Add element
fruits+=("grape")

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

# Associative array (dictionary)
declare -A colors
colors[apple]="red"
colors[banana]="yellow"

echo "${colors[apple]}"    # red

# Iterate associative array
for key in "${!colors[@]}"; do
    echo "$key: ${colors[$key]}"
done

String Manipulation

#!/bin/bash

text="Hello, World!"

# Length
echo ${#text}

# Substring
echo ${text:7:5}    # "World"

# Replace
echo ${text/World/Bash}    # "Hello, Bash!"

# Uppercase/Lowercase
echo ${text^^}      # HELLO, WORLD!
echo ${text,,}      # hello, world!

# Remove prefix/suffix
file="document.txt"
echo ${file%.txt}   # "document"
echo ${file#doc}    # "ument.txt"

File Operations in Scripts

Reading Files

#!/bin/bash

# Read entire file
content=$(cat file.txt)

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

# Read into array
mapfile -t lines < file.txt

Writing Files

#!/bin/bash

# Overwrite file
echo "New content" > file.txt

# Append to file
echo "Additional content" >> file.txt

# Here document
cat > config.txt << EOF
Setting1=value1
Setting2=value2
EOF

Error Handling

Exit Status

#!/bin/bash

# Check last command status
ls /nonexistent 2>/dev/null
if [ $? -ne 0 ]; then
    echo "Command failed"
fi

# Exit with status code
if [ ! -f "$FILE" ]; then
    echo "Error: File not found"
    exit 1
fi

Error Handling Patterns

#!/bin/bash

# Exit on error
set -e

# Exit on undefined variable
set -u

# Exit on pipe failure
set -o pipefail

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

# Cleanup on exit
trap 'rm -f /tmp/tempfile' EXIT

Practical Examples

Backup Script

#!/bin/bash

# Backup script
SOURCE="/var/www"
DEST="/backup"
DATE=$(date +%Y%m%d-%H%M%S)
FILENAME="backup-$DATE.tar.gz"

# Create backup
tar -czf "$DEST/$FILENAME" "$SOURCE"

# Check if successful
if [ $? -eq 0 ]; then
    echo "Backup successful: $FILENAME"

    # Clean old backups (keep last 7 days)
    find "$DEST" -name "backup-*.tar.gz" -mtime +7 -delete
else
    echo "Backup failed!" >&2
    exit 1
fi

System Monitor

#!/bin/bash

# System monitoring script
while true; do
    clear
    echo "=== System Monitor ==="
    echo "Time: $(date)"
    echo ""

    echo "CPU Usage:"
    top -bn1 | grep "Cpu(s)" | awk '{print $2 "%"}'

    echo "Memory Usage:"
    free -h | awk '/^Mem:/ {print $3 "/" $2}'

    echo "Disk Usage:"
    df -h | grep "^/dev" | awk '{print $1 ": " $5}'

    sleep 5
done

User Management Script

#!/bin/bash

# User management script
create_user() {
    local username=$1
    local fullname=$2

    # Check if user exists
    if id "$username" &>/dev/null; then
        echo "User $username already exists"
        return 1
    fi

    # Create user
    sudo useradd -m -c "$fullname" "$username"

    # Set password
    echo "$username:temp123" | sudo chpasswd

    # Force password change
    sudo chage -d 0 "$username"

    echo "User $username created successfully"
}

# Usage
create_user "jdoe" "John Doe"

Best Practices

Script Template

#!/bin/bash
#
# Script Name:     myscript.sh
# Description:     Brief description of what the script does
# Author:          Your Name
# Date:            2025-02-07
# Version:         1.0
#

# Strict mode
set -euo pipefail

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

# Functions
usage() {
    cat << EOF
Usage: $SCRIPT_NAME [OPTIONS] <argument>

Description of the script.

OPTIONS:
    -h, --help      Show this help message
    -v, --verbose   Enable verbose mode
    -d, --debug     Enable debug mode

EXAMPLES:
    $SCRIPT_NAME file.txt
    $SCRIPT_NAME -v file.txt
EOF
}

main() {
    # Main script logic here
    echo "Script running..."
}

# Run main function
main "$@"

Coding Standards

# Use meaningful variable names
user_name="john"  # Good
x="john"          # Bad

# Quote variables
rm "$file"        # Good
rm $file          # Bad (fails with spaces)

# Use [[ ]] instead of [ ]
if [[ "$var" == "value" ]]; then  # Good
    echo "Match"
fi

# Use lowercase for local variables
local_var="value"

# Use UPPERCASE for constants  
readonly MAX_USERS=100

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

Debugging Scripts

Debug Techniques

# Debug mode (print each command)
bash -x script.sh

# Or in script
set -x

# Specific section
set -x
# Debug this section
set +x

# Verbose mode
set -v

# Check syntax without executing
bash -n script.sh

# Print to stderr
echo "Error message" >&2

# Custom debug function
DEBUG=true
debug() {
    if [[ "$DEBUG" == "true" ]]; then
        echo "[DEBUG] $*" >&2
    fi
}

debug "This is a debug message"

Quick Reference

# Variables
var="value"                  # Assign
echo "$var"                  # Use variable
readonly var="value"         # Constant

# Conditionals
if [[ condition ]]; then
    # commands
fi

# Loops
for item in list; do
    # commands
done

while [[ condition ]]; do
    # commands
done

# Functions
function_name() {
    # commands
}

# Arrays
arr=(item1 item2)
echo "${arr[0]}"            # Access
echo "${arr[@]}"            # All items
echo "${#arr[@]}"           # Length

# String operations
${#var}                     # Length
${var:offset:length}        # Substring
${var//find/replace}        # Replace

# File tests
-e file                     # Exists
-f file                     # Is file
-d file                     # Is directory
-r file                     # Readable