Linux Tutorial — Part 7: Shell Scripting

By Suraj Ahir December 09, 2025 6 min read

Linux — Package Management
Linux — Package Management
← Part 6 Linux Tutorial · Part 7 of 12 Part 8 →

Shell scripting is where the Linux command line transforms from interactive tool to automation engine. Scripts let you combine commands, add logic, schedule tasks, and automate entire workflows. Every DevOps and system administration role requires shell scripting. This is the skill that saves hours every week.

Creating Your First Script

Hello World Script
#!/bin/bash
# The shebang line tells Linux which interpreter to use

echo "Hello from my first script!"
echo "Today is: $(date)"
echo "You are logged in as: $USER"
echo "Your home directory is: $HOME"
Make It Executable and Run
chmod +x hello.sh     # Make executable
./hello.sh            # Run it
bash hello.sh         # Alternative: run with bash directly

Variables in Scripts

Variables
#!/bin/bash
APP_NAME="MyWebApp"
VERSION="2.1.0"
DEPLOY_DIR="/var/www/$APP_NAME"

echo "Deploying $APP_NAME version $VERSION to $DEPLOY_DIR"

# Command substitution — capture command output
CURRENT_DATE=$(date +%Y-%m-%d)
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}')
echo "Deployment date: $CURRENT_DATE"
echo "Disk usage: $DISK_USAGE"

Reading User Input

read Command
#!/bin/bash
echo "Enter your name:"
read NAME

echo "Enter environment (dev/staging/prod):"
read -r ENV

echo "Hello $NAME! Deploying to $ENV environment."

Conditionals in Scripts

if-else in Bash
#!/bin/bash
DISK_PERCENT=$(df / | awk 'NR==2 {gsub(/%/,""); print $5}')

if [ $DISK_PERCENT -gt 90 ]; then
    echo "CRITICAL: Disk at $DISK_PERCENT% — immediate action needed"
elif [ $DISK_PERCENT -gt 75 ]; then
    echo "WARNING: Disk at $DISK_PERCENT% — clean up soon"
else
    echo "OK: Disk at $DISK_PERCENT%"
fi

Loops in Scripts

for and while Loops
#!/bin/bash

# Loop through list
for SERVER in web1 web2 web3 db1; do
    echo "Checking $SERVER..."
    ping -c 1 $SERVER > /dev/null 2>&1 && echo "$SERVER: UP" || echo "$SERVER: DOWN"
done

# Loop through files
for FILE in /var/log/*.log; do
    LINES=$(wc -l < "$FILE")
    echo "$FILE has $LINES lines"
done

# While loop
COUNTER=1
while [ $COUNTER -le 5 ]; do
    echo "Attempt $COUNTER"
    COUNTER=$((COUNTER + 1))
done

Functions in Scripts

Bash Functions
#!/bin/bash

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

check_service() {
    SERVICE=$1
    if systemctl is-active --quiet $SERVICE; then
        log "$SERVICE is running"
        return 0
    else
        log "$SERVICE is NOT running"
        return 1
    fi
}

# Use the functions
log "Starting health check"
check_service nginx
check_service postgresql
log "Health check complete"

A Real Deployment Script

Production-Style Deployment Script
#!/bin/bash
set -e  # Exit immediately on any error

APP="srjahir-tech"
DEPLOY_DIR="/var/www/$APP"
BACKUP_DIR="/var/backups/$APP"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

log() { echo "[INFO] $(date +%T) $1"; }
error() { echo "[ERROR] $1" >&2; exit 1; }

log "Starting deployment of $APP"

# Check required tools
command -v git > /dev/null || error "git not found"
command -v python3 > /dev/null || error "python3 not found"

# Backup current version
log "Creating backup..."
cp -r $DEPLOY_DIR $BACKUP_DIR/backup_$TIMESTAMP

# Pull latest code
log "Pulling latest code..."
cd $DEPLOY_DIR
git pull origin main

# Install dependencies
log "Installing dependencies..."
pip3 install -r requirements.txt -q

# Restart service
log "Restarting $APP service..."
sudo systemctl restart $APP

sleep 2
systemctl is-active --quiet $APP && log "Deployment successful!" || error "Service failed to start"

In Part 8, we cover cron jobs — how to schedule scripts to run automatically at specified intervals.

Functions in Shell Scripts

Shell scripts support functions — named, reusable blocks of code that make scripts organized and maintainable:

Shell Functions
#!/bin/bash

log_message() {
    local level="$1"
    local message="$2"
    echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $message"
}

check_disk_space() {
    local threshold=${1:-80}
    local usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
    if [ "$usage" -gt "$threshold" ]; then
        log_message "WARN" "Disk usage ${usage}% exceeds threshold ${threshold}%"
        return 1
    fi
    log_message "INFO" "Disk usage ${usage}% is within limit"
    return 0
}

check_disk_space 75

In bash, functions use local to declare local variables. The return value is a numeric exit code — 0 for success, non-zero for failure. To return data from a function, echo the value inside the function and capture it with: result=$(my_function).

Script Debugging and Best Practices

Use bash -n script.sh to check syntax without executing. Use bash -x script.sh to run with execution tracing — every command prints before executing with variables expanded. Add set -e at the top to exit immediately if any command fails. Add set -u to treat unset variables as errors. Add set -o pipefail to catch failures in pipes. These three together (set -euo pipefail) are the standard defensive shell scripting header that prevents silent failures.

Practice Exercise

Write a system health check script using functions: one function checks disk usage and warns above 85%, another checks if memory available is below 500MB, another checks if a specified service is running using systemctl is-active. The main script calls all three functions, logs results to a file with timestamps, and exits with code 1 if any check fails. Test with different thresholds to verify both pass and fail paths.

Linux in Your Daily Engineering Practice

Linux command-line proficiency is not something you learn once and then stop improving. It is a skill that deepens continuously as you encounter new tools, new use cases, and new problems to solve. The engineers who are most effective at the command line did not become that way by reading comprehensive guides — they became that way by spending years solving real problems at the terminal, gradually accumulating a toolkit of commands, aliases, scripts, and muscle memory. The best approach is to use Linux for real work as much as possible, to look up better ways to do things you already do, and to make note of efficient patterns you observe in others' work. Over time, the terminal becomes faster than any GUI for the tasks you do repeatedly.

Putting It All Together

Every topic in technology and finance rewards the learner who goes beyond surface understanding to build genuine fluency. Fluency comes from repeated exposure, application in varied contexts, and reflection on what worked and what did not. The concepts discussed here are starting points — each one opens into a deeper field of study that could occupy years of focused learning. The most effective approach is not to try to master everything at once, but to pick the areas most relevant to your current goals, go deep there, and then expand. Depth in a few areas is more valuable than shallow familiarity with many. Build on what you know, stay curious about what you do not, and keep the practice of learning as a consistent daily habit rather than an occasional burst of effort.

The questions that make learning stick: How does this connect to what I already know? Where would I actually use this? What would happen if I tried to explain this to someone who knows nothing about it? What are the edge cases and exceptions? What is still unclear? Asking these questions transforms passive reading into active learning, and active learning is what builds the kind of understanding that is still accessible years later when you need it under real conditions.

Disclaimer: Educational content only. Practice is required for real skill development.