Shell Scripting — Bash Fundamentals
IntermediateBash scripts automate repetitive tasks, deploy software, and glue tools together. Master variables, conditionals, loops, functions, and error handling to write production-grade scripts.
Overview
Bash is the default shell on most Linux systems and the de-facto language for DevOps automation. Shell scripts are executed top-to-bottom by the bash interpreter. Unlike compiled languages, bash is dynamically typed (all values are strings), weak on error handling by default (set -e helps), and glues Unix tools together via pipes and redirects. The goal of a shell script is not to rewrite Python — it is to orchestrate existing CLI tools efficiently. Understanding when to use bash vs Python is as important as knowing bash syntax.
Script Skeleton & Error Handling
Every production shell script should start with a shebang and safety flags. set -euo pipefail is the standard "strict mode" that catches common errors.
#!/usr/bin/env bash
# ↑ shebang: tells OS which interpreter to use (/usr/bin/env finds bash in PATH)
set -e # exit immediately on any error (non-zero exit code)
set -u # treat unset variables as errors (instead of empty string)
set -o pipefail # pipe fails if ANY command in the pipe fails (not just last)
# Shorthand: set -euo pipefail
# Debugging mode (print every command before executing)
set -x # enable debug tracing
set +x # disable debug tracing
# Script info
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_NAME="$(basename "$0")"
# Trap for cleanup on exit
cleanup() {
echo "Cleaning up..."
rm -f /tmp/my-lock-file
}
trap cleanup EXIT # runs cleanup() on any exit (normal or error)
trap 'echo "Error at line $LINENO"' ERR # print line number on error
echo "Script started from: $SCRIPT_DIR"Variables, Conditionals & Loops
Bash variables are untyped strings. Conditionals use [ ] or [[ ]] (prefer [[ ]]). Loops iterate over arrays, ranges, and command output.
#!/usr/bin/env bash
set -euo pipefail
# Variables (no spaces around =)
NAME="Akshay"
AGE=30
GREETING="Hello, $NAME! You are $AGE."
echo "$GREETING"
# Command substitution
CURRENT_DATE=$(date +%Y-%m-%d)
FILE_COUNT=$(ls -1 /var/log/*.log | wc -l)
# Conditionals — prefer [[ ]] over [ ] (supports && || without escaping)
if [[ "$NAME" == "Akshay" ]]; then
echo "Hello, Akshay"
elif [[ $AGE -gt 18 ]]; then # -gt (>), -lt (<), -eq (==), -ne (!=) for numbers
echo "Adult"
else
echo "Other"
fi
# File tests
if [[ -f "/etc/nginx/nginx.conf" ]]; then # -f: file exists
echo "nginx.conf found"
fi
# -d: directory -e: exists -r: readable -w: writable -x: executable
# -z: string empty -n: string non-empty
# For loops
for i in 1 2 3 4 5; do
echo "Item: $i"
done
for file in /var/log/*.log; do
echo "Processing: $file"
done
for i in $(seq 1 10); do # seq generates a range
echo "$i"
done
# While loop
COUNT=0
while [[ $COUNT -lt 5 ]]; do
echo "Count: $COUNT"
((COUNT++))
doneFunctions, Arguments & Real-World Pattern
Functions promote reuse and readability. Arguments ($1, $@) make scripts configurable. Here is a real deploy script pattern.
#!/usr/bin/env bash
set -euo pipefail
# Functions
log_info() { echo "[INFO] $(date +%H:%M:%S) $*"; }
log_error() { echo "[ERROR] $(date +%H:%M:%S) $*" >&2; } # >&2 = stderr
check_requirements() {
local -a required=("docker" "git" "curl")
for cmd in "${required[@]}"; do
if ! command -v "$cmd" &>/dev/null; then
log_error "Required command not found: $cmd"
exit 1
fi
done
log_info "All requirements met"
}
# Script arguments
# $0 = script name, $1 $2 ... = positional args
# $# = number of args, $@ = all args, $* = all args as one string
usage() {
echo "Usage: $0 <environment> <image-tag>"
echo " environment: staging | production"
echo " image-tag: docker image tag to deploy"
exit 1
}
[[ $# -lt 2 ]] && usage # exit with usage if fewer than 2 args
ENV="$1"
TAG="$2"
deploy() {
local env="$1"
local tag="$2"
log_info "Deploying $tag to $env..."
docker pull "myapp:$tag"
docker stop "myapp-$env" 2>/dev/null || true # ignore if not running
docker run -d --name "myapp-$env" --restart=unless-stopped "myapp:$tag"
log_info "Deploy complete"
}
check_requirements
deploy "$ENV" "$TAG"Key Points to Remember
- 1Always start scripts with #!/usr/bin/env bash and set -euo pipefail.
- 2Use [[ ]] instead of [ ] — it supports &&, ||, regex, and does not split on spaces.
- 3Quote all variable expansions: "$VAR" not $VAR — prevents word splitting on spaces.
- 4Use local keyword inside functions to avoid polluting global scope.
- 5$? is the exit code of the last command; 0 = success, non-zero = failure.
- 6Redirect errors to stderr with >&2 so they can be separated from normal output.
Interview Questions
Sign in to ask AriaWhat does set -euo pipefail do in a bash script?
What is the difference between $@ and $* in bash?
Ask Aria about Shell Scripting — Bash Fundamentals
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.