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}"
| Syntax | Description | Example |
|---|---|---|
$VAR | Variable reference | echo $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, $2 | Positional parameters | ./script.sh arg1 arg2 |
$@ | All arguments (individually) | for arg in "$@" |
$# | Number of arguments | if [ $# -eq 0 ] |
$? | Exit code of last command | 0=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 Comparison | Meaning | File Test | Meaning |
|---|---|---|---|
-eq | Equal | -f | File exists |
-ne | Not equal | -d | Directory exists |
-gt | Greater than | -r | Has read permission |
-ge | Greater or equal | -w | Has write permission |
-lt | Less than | -x | Has execute permission |
-le | Less or equal | -s | File 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.
| Option | Effect |
|---|---|
-e | Exit script immediately on command failure |
-u | Error on undefined variable usage |
-o pipefail | Entire 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 withchmod +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
localfor local variables in functions,returnfor exit codes - Always include
set -euo pipefailin production scripts - Combining CLI tools like
find,awk, andgrepenables powerful automation