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.
#!/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"
chmod +x hello.sh # Make executable
./hello.sh # Run it
bash hello.sh # Alternative: run with bash directly
#!/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"
#!/bin/bash
echo "Enter your name:"
read NAME
echo "Enter environment (dev/staging/prod):"
read -r ENV
echo "Hello $NAME! Deploying to $ENV environment."
#!/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
#!/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
#!/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"
#!/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.
Shell scripts support functions — named, reusable blocks of code that make scripts organized and maintainable:
#!/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).
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.
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 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.
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.