Shell Scripting Locking Database Centric Advanced May 2026

Shell Scripting Database Centric: Shell Script Locking & Concurrency

Prevent concurrent execution with flock, PID files, and advisory locks. Handle database-backed distributed locks, implement safe parallel data processing with semaphores, and coordinate multiple scripts safely.

When cron jobs overlap, when multiple deploy agents run simultaneously, when parallel workers all try to migrate the same database — concurrency bugs can corrupt data or cause double-processing. Shell scripts have three built-in locking primitives: flock, PID files, and directory atomicity. Knowing which to use and when is essential production knowledge.

BASH
# flock acquires a kernel file lock — cleaned up automatically on process exit
# even if the script crashes or is killed with SIGKILL

# ── Simplest form: wrap entire script ─────────────────────
flock -n /var/lock/myapp-backup.lock ./backup.sh
# -n: non-blocking — exit immediately if lock is held
# Without -n: block (wait) until lock is released

# ── With timeout ──────────────────────────────────────────
flock -w 10 /var/lock/myapp.lock ./migrate.sh
# -w 10: wait up to 10 seconds for the lock, then exit

# ── Inside a script using a file descriptor ───────────────
exec 200>/var/lock/myapp.lock
if ! flock -n 200; then
  echo "Another instance is running. Exiting." >&2
  exit 1
fi
# ... protected critical section here ...
# Lock released automatically when fd 200 closes (script exits)

# ── Self-locking script header pattern ────────────────────
[ "${FLOCKER}" != "$0" ] && \
  exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
# Place at top of any script — it re-executes itself with a lock on itself

# ── Flock with cleanup notification ───────────────────────
exec 9>/var/lock/daily_report.lock
flock -w 5 9 || { echo "Cannot acquire lock"; exit 1; }
trap 'flock -u 9; rm -f /var/lock/daily_report.lock' EXIT
echo "Lock acquired by PID $$ at $(date)" >&9
BASH
#!/usr/bin/env bash
# PID file locking — checks if the previous instance is still alive

PID_FILE="/var/run/myapp-worker.pid"

acquire_pidlock() {
  if [[ -f "${PID_FILE}" ]]; then
    OLD_PID=$(cat "${PID_FILE}")
    if kill -0 "${OLD_PID}" 2>/dev/null; then
      echo "Already running as PID ${OLD_PID}" >&2
      return 1
    fi
    # Stale PID file — previous run crashed
    echo "Removing stale PID file (PID ${OLD_PID} no longer running)"
  fi
  echo "$$" > "${PID_FILE}"
  trap 'rm -f "${PID_FILE}"' EXIT INT TERM
}

acquire_pidlock || exit 1

echo "Worker running as PID $$"
# ... do work ...

# ── Check if another instance is running ───────────────────
is_running() {
  [[ -f "${PID_FILE}" ]] && kill -0 "$(cat "${PID_FILE}")" 2>/dev/null
}

if is_running; then
  echo "Service is running (PID $(cat "${PID_FILE}"))"
else
  echo "Service is NOT running"
fi
BASH
# ── Process a list with N workers in parallel ─────────────
MAX_PARALLEL=4

process_item() {
  local item="${1}"
  echo "Processing: ${item}"
  sleep 1  # simulate work
}

# Method 1: xargs parallel ────────────────────────────────
cat items.txt | xargs -P "${MAX_PARALLEL}" -I{} bash -c 'process_item "$@"' -- {}

# Method 2: background jobs with wait ─────────────────────
running=0
while read -r item; do
  process_item "${item}" &
  (( ++running ))
  (( running >= MAX_PARALLEL )) && { wait -n 2>/dev/null || wait; (( running-- )); }
done < items.txt
wait  # wait for remaining jobs

# Method 3: GNU parallel (most powerful) ──────────────────
parallel -j4 process_item {} ::: $(cat items.txt)
cat items.txt | parallel -j4 './worker.sh {}'

# ── Semaphore with flock ───────────────────────────────────
# Allow at most N simultaneous processes using numbered lock files
run_with_semaphore() {
  local sem_dir="/tmp/myapp-sem"
  local max="${1}"; shift
  mkdir -p "${sem_dir}"
  for i in $(seq 1 "${max}"); do
    if flock -n "${sem_dir}/slot_${i}" "$@"; then
      return 0
    fi
  done
  echo "Semaphore full — waiting for slot"
  flock "${sem_dir}/slot_1" "$@"
}
flock — concurrent cron protection
vriddh@prod-01:~/scripts$flock -n /var/lock/backup.lock ./backup.sh & flock -n /var/lock/backup.lock ./backup.sh
[1] 18421 Backup started...
flock: /var/lock/backup.lock: already locked by another process
vriddh@prod-01:~/scripts$cat items.txt | xargs -P4 -I{} ./process.sh {}
Processing item-01 (PID 18501)
Processing item-02 (PID 18502)
Processing item-03 (PID 18503)
Processing item-04 (PID 18504)
✔ Locking rules — Prefer flock over PID files for single-machine locking — it is automatically released even on SIGKILL. Use PID files when you also need to send signals to the running process. Use flock -n for cron jobs that should skip if already running, and flock -w N for jobs that should wait a bounded time. Always pair flock with trap ... EXIT to clean up lock files. For distributed locking across machines, use Redis SET NX EX (page 59).