Linux Shell Scripting (Bash) Guide

What Is Shell Scripting?

A shell script is a file containing a collection of commands that runs in a Linux/Unix shell. Saved with the .sh extension, it’s an essential tool for server administration, deployment automation, cron jobs, and other system operations. This guide covers Bash (Bourne Again Shell), the default shell on most Linux distributions.

This article covers basic syntax, variables, conditionals, loops, functions, and practical automation scripts.

Your First Script

The first line of a shell script starts with the shebang #!/bin/bash. This tells the operating system which interpreter to use for execution.

#!/bin/bash
# hello.sh — your first shell script

echo "Hello, this is a shell script!"
echo "Current user: $(whoami)"
echo "Current path: $(pwd)"
echo "Today's date: $(date '+%Y-%m-%d %H:%M')"

You need to grant execute permission to run the script.

# Grant execute permission and run
chmod +x hello.sh
./hello.sh
# Hello, this is a shell script!
# Current user: ubuntu
# Current path: /home/ubuntu
# Today's date: 2026-04-07 22:00

The $(command) syntax substitutes the command’s output as a string. It works the same as backticks (`command`), but the $() form is recommended since it supports nesting.

Variables

When declaring variables, there must be no spaces around =. Use $variable or ${variable} to reference them.

#!/bin/bash
# Variable declaration (no spaces around =!)
PROJECT="my-app"
VERSION="2.1.0"
BUILD_DIR="/opt/${PROJECT}/build"

echo "Project: ${PROJECT} v${VERSION}"
echo "Build path: ${BUILD_DIR}"

# Read-only variable
readonly MAX_RETRY=3

# Export environment variable (pass to child processes)
export API_KEY="your-secret-key"

# User input
read -p "Select deployment environment (dev/prod): " ENV
echo "Selected environment: ${ENV}"
SyntaxDescriptionExample
$VARVariable referenceecho $HOME
${VAR}Explicit reference (recommended)echo ${HOME}/bin
${VAR:-default}Default value if unset${PORT:-8080}
${VAR:?error}Error if unset${DB_HOST:?required}
${#VAR}String length${#PROJECT} -> 6
$1, $2Positional parameters./script.sh arg1 arg2
$@All arguments (individually)for arg in "$@"
$#Number of argumentsif [ $# -eq 0 ]
$?Exit code of last command0=success, 1+=failure

Conditionals

Bash conditionals use the form if [ condition ] or if [[ condition ]]. [[ ]] is a Bash extension that supports pattern matching and regular expressions.

#!/bin/bash
# Conditional examples

# Numeric comparison
COUNT=15
if [ "${COUNT}" -gt 10 ]; then
    echo "${COUNT} is greater than 10"
elif [ "${COUNT}" -eq 10 ]; then
    echo "${COUNT} equals 10"
else
    echo "${COUNT} is less than 10"
fi

# String comparison
ENV="production"
if [[ "${ENV}" == "production" ]]; then
    echo "Production environment — proceed with caution"
fi

# File/directory checks
CONFIG="/etc/app/config.yaml"
if [ -f "${CONFIG}" ]; then
    echo "Config file exists: ${CONFIG}"
elif [ -d "/etc/app" ]; then
    echo "Directory exists but config file is missing"
else
    echo "Directory does not exist"
fi
Numeric ComparisonMeaningFile TestMeaning
-eqEqual-fFile exists
-neNot equal-dDirectory exists
-gtGreater than-rHas read permission
-geGreater or equal-wHas write permission
-ltLess than-xHas execute permission
-leLess or equal-sFile size > 0

Loops and Functions

Combining for, while loops with functions enables complex automation logic.

#!/bin/bash
# Loop and function examples

# for: iterate over array
SERVERS=("web01" "web02" "db01" "cache01")
for server in "${SERVERS[@]}"; do
    echo "Checking server: ${server}"
done

# for: numeric range
for i in {1..5}; do
    echo "Count: ${i}"
done

# while: conditional loop (read file line by line)
while IFS= read -r line; do
    echo "Read line: ${line}"
done < /etc/hostname

# Function definition and call
log() {
    local level="$1"
    local message="$2"
    echo "[$(date '+%H:%M:%S')] [${level}] ${message}"
}

check_service() {
    local service="$1"
    if systemctl is-active --quiet "${service}"; then
        log "INFO" "${service} is running normally"
        return 0
    else
        log "WARN" "${service} is stopped"
        return 1
    fi
}

# Function call
check_service "nginx"

Using the local keyword inside functions declares local variables that don’t affect variables outside the function. Functions return exit codes (0-255) via return. For string return values, combine echo with $().

Practical Script: Log Cleanup Automation

A script that compresses and deletes old log files in a specified directory. Running it daily via cron job automatically manages disk space.

#!/bin/bash
# log-cleanup.sh — automated log file cleanup
set -euo pipefail  # Exit on error, error on undefined variables, propagate pipe errors

LOG_DIR="${1:-/var/log/app}"
DAYS_TO_COMPRESS=7
DAYS_TO_DELETE=30
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

log() { echo "[${TIMESTAMP}] $1"; }

# Verify directory exists
if [ ! -d "${LOG_DIR}" ]; then
    log "Error: directory ${LOG_DIR} does not exist"
    exit 1
fi

# 1. Compress logs older than 7 days -> gzip
COMPRESS_COUNT=$(find "${LOG_DIR}" -name "*.log" -mtime +${DAYS_TO_COMPRESS} -type f | wc -l)
if [ "${COMPRESS_COUNT}" -gt 0 ]; then
    find "${LOG_DIR}" -name "*.log" -mtime +${DAYS_TO_COMPRESS} -type f -exec gzip {} \;
    log "${COMPRESS_COUNT} log file(s) compressed"
else
    log "No files to compress"
fi

# 2. Delete compressed files older than 30 days
DELETE_COUNT=$(find "${LOG_DIR}" -name "*.gz" -mtime +${DAYS_TO_DELETE} -type f | wc -l)
if [ "${DELETE_COUNT}" -gt 0 ]; then
    find "${LOG_DIR}" -name "*.gz" -mtime +${DAYS_TO_DELETE} -type f -delete
    log "${DELETE_COUNT} compressed file(s) deleted"
else
    log "No files to delete"
fi

# 3. Report disk usage
USAGE=$(du -sh "${LOG_DIR}" | awk '{print $1}')
log "Current log directory size: ${USAGE}"

set -euo pipefail is a safety net for Bash scripts and is always recommended for production scripts.

OptionEffect
-eExit script immediately on command failure
-uError on undefined variable usage
-o pipefailEntire pipeline fails if any command in it fails

Cron job registration example: In crontab -e, add 0 2 * * * /opt/scripts/log-cleanup.sh /var/log/app >> /var/log/cleanup.log 2>&1 — runs daily at 2:00 AM.

Summary

Shell scripting is an essential tool for Linux server administration. Here are the key takeaways:

  • Start with #!/bin/bash, grant execute permission with chmod +x
  • No spaces around = for variables, use ${variable} braces when referencing
  • Conditionals use [ ] or [[ ]], numeric comparison with -eq/-gt, file tests with -f/-d
  • Use local for local variables in functions, return for exit codes
  • Always include set -euo pipefail in production scripts
  • Combining CLI tools like find, awk, and grep enables powerful automation

Was this article helpful?