Bash scripts let you automate repetitive tasks, build deployment pipelines, and string together complex command sequences. This beginner guide covers everything you need to write real, useful scripts from scratch — no prior scripting experience required.
Create a file with a .sh extension, add a shebang line, write your commands, and make it executable:
#!/usr/bin/env bash
# My first script
echo "Hello, $(whoami)!"
echo "Today is $(date +%Y-%m-%d)"
chmod +x hello.sh
./hello.sh
The shebang line (#!/usr/bin/env bash) tells the OS which interpreter to use. Using env bash is more portable than #!/bin/bash because it finds bash wherever it is installed.
#!/usr/bin/env bash
# Assign (no spaces around =)
name="Alice"
age=30
today=$(date +%Y-%m-%d) # Command substitution
# Use with ${} (best practice) or $
echo "Name: ${name}"
echo "Age: ${age}"
echo "Date: ${today}"
# Read-only variable
readonly MAX_RETRIES=3
# Environment variable
export DATABASE_URL="postgres://localhost/mydb"
"${var}". Unquoted variables break if they contain spaces.#!/usr/bin/env bash
# Positional arguments: $1, $2, $3...
# $0 is the script name, $# is argument count, $@ is all arguments
if [ $# -lt 1 ]; then
echo "Usage: $0 "
exit 1
fi
username="$1"
echo "Creating user: ${username}"
# Read user input interactively
read -p "Enter password: " -s password
echo # newline after silent input
echo "Password set."
#!/usr/bin/env bash
file="/etc/nginx/nginx.conf"
# File tests
if [ -f "${file}" ]; then
echo "File exists"
elif [ -d "${file}" ]; then
echo "It's a directory"
else
echo "Not found"
fi
# String comparison
env="${1:-development}" # Default to 'development' if $1 is empty
if [ "${env}" = "production" ]; then
echo "Running in production mode"
fi
# Numeric comparison
count=$(ls | wc -l)
if [ "${count}" -gt 10 ]; then
echo "More than 10 files"
fi
Common test operators: -f (file exists), -d (directory), -z (string is empty), -n (string is not empty), -eq -ne -gt -lt (numeric comparisons).
#!/usr/bin/env bash
# Loop over a list of values
for env in development staging production; do
echo "Deploying to ${env}..."
done
# Loop over files
for file in *.log; do
echo "Processing: ${file}"
done
# While loop
count=0
while [ ${count} -lt 5 ]; do
echo "Attempt ${count}"
((count++))
done
# Loop over command output
while IFS= read -r line; do
echo "Line: ${line}"
done < /etc/hosts
#!/usr/bin/env bash
log() {
local level="$1"
local message="$2"
echo "[$(date +%H:%M:%S)] [${level}] ${message}"
}
create_backup() {
local source_dir="$1"
local backup_name="backup-$(date +%Y%m%d-%H%M%S).tar.gz"
tar -czf "/tmp/${backup_name}" "${source_dir}"
log "INFO" "Backup created: ${backup_name}"
echo "${backup_name}" # Return value via stdout
}
log "INFO" "Script started"
result=$(create_backup "/var/www/html")
log "INFO" "Backup file: ${result}"
By default, bash continues executing after a command fails. Add these options at the top of every script:
#!/usr/bin/env bash
set -euo pipefail
# -e: exit immediately if a command fails
# -u: treat unset variables as errors
# -o pipefail: propagate pipe failures (not just last command)
trap 'echo "Error on line ${LINENO}. Exit code: $?"' ERR
trap 'echo "Script interrupted"' INT TERM
# Now if any command fails, the script stops and reports the error
git pull origin main
npm ci
npm run build
echo "Build successful"
#!/usr/bin/env bash
set -euo pipefail
APP_DIR="/opt/myapp"
BRANCH="${1:-main}"
log() { echo "[$(date +%H:%M:%S)] $*"; }
log "Starting deployment from branch: ${BRANCH}"
cd "${APP_DIR}"
git fetch origin
git checkout "${BRANCH}"
git pull origin "${BRANCH}"
log "Installing dependencies..."
npm ci --production
log "Running database migrations..."
npm run db:migrate
log "Restarting application..."
pm2 restart myapp
log "Deployment complete!"
# Check if a command exists
if ! command -v jq &> /dev/null; then
echo "jq is required but not installed"; exit 1
fi
# Get the directory of the script itself
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Default value for variable
PORT="${PORT:-8080}"
# Redirect stdout and stderr to a log file
exec > >(tee -a "/var/log/deploy.log") 2>&1