Bash Scripting for Beginners: Automate Your Workflow

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.

Bash Shell Scripting Linux Automation

Your First Bash Script

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.

Variables

#!/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"
💡 Always quote variable expansions: "${var}". Unquoted variables break if they contain spaces.

Input: Reading Arguments and User Input

#!/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."

Conditionals

#!/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).

Loops

#!/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

Functions

#!/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}"

Error Handling

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"

A Complete Deployment Script Example

#!/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!"

Useful One-Liners for Scripts

# 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

Related Snippets