Security

ia-linux-bash-scripting

Try it

Defensive Bash scripting for Linux: safe foundations, argument parsing, production patterns, ShellCheck compliance. Use when writing bash scripts, shell scripts, cron jobs, or CLI tools in bash.

What it does

Produce bash scripts that pass and with zero warnings.

The skill document

Linux Bash Scripting

Produce bash scripts that pass shellcheck --enable=all and shfmt -d with zero warnings.

Target: GNU Bash 4.4+ on Linux. No macOS/BSD workarounds, no Windows paths, no POSIX-only restrictions.

Script Foundation

#!/usr/bin/env bash
set -Eeuo pipefail
shopt -s inherit_errexit

readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

trap 'printf "Error at %s:%d\n" "${BASH_SOURCE[0]}" "$LINENO" >&2' ERR
trap 'rm -rf -- "${_tmpdir:-}"' EXIT
  • -E propagates ERR traps into functions
  • inherit_errexit propagates errexit into $() command substitutions
  • Always create temp dirs under the EXIT trap: _tmpdir=$(mktemp -d)
  • Wrap body in main() { ... } with source guard: [[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@" -- enables sourcing for testing

Core Rules

  • Quote every expansion: "$var", "$(cmd)", "${array[@]}"
  • local for function variables, local -r for function constants, readonly for script constants
  • printf '%s\n' over echo -- predictable behavior, no flag interpretation
  • [[ ]] for conditionals; (( )) for arithmetic; $() over backticks
  • End options with --: rm -rf -- "$path", grep -- "$pattern" "$file"
  • Require env vars: : "${VAR:?must be set}"
  • Never eval user input; build commands as arrays: cmd=("grep" "--" "$pat" "$f"); "${cmd[@]}"
  • Keep untrusted/derived bytes off the command line: never build a heredoc body or an sh -c string from external data. An unquoted `<&2; exit 1 ;; *) break ;; esac done

## Production Patterns

**Dependency check:**
```bash
require() { command -v "$1" &>/dev/null || { printf 'Missing: %s\n' "$1" >&2; exit 1; }; }
require jq; require curl

Dry-run wrapper:

run() { if [[ "${DRY_RUN:-}" == "1" ]]; then printf '[dry] %s\n' "$*" >&2; else "$@"; fi; }
run cp "$src" "$dst"

Atomic file write -- write to temp, rename into place:

atomic_write() { local tmp; tmp=$(mktemp); cat >"$tmp"; mv -- "$tmp" "$1"; }
generate_config | atomic_write /etc/app/config.yml

Retry with backoff:

retry() { local n=0 max=5 delay=1; until "$@"; do ((++n>=max)) && return 1; sleep $delay; ((delay*=2)); done; }
retry curl -fsSL "$url"

Script locking -- prevent concurrent runs:

exec 9>/var/lock/"${0##*/}".lock
flock -n 9 || { printf 'Already running\n' >&2; exit 1; }

Idempotent operations -- safe to rerun:

ensure_dir()  { [[ -d "$1" ]] || mkdir -p -- "$1"; }
ensure_link() { [[ -L "$2" ]] || ln -s -- "$1" "$2"; }

Input validation: [[ "$1" =~ ^[1-9][0-9]*$ ]] || die "Invalid: $1" -- validate at script boundaries with [[ =~ ]]

  • umask 077 for scripts creating sensitive files
  • Signal cleanup: trap 'cleanup; exit 130' INT TERM -- preserves correct exit codes for callers

Logging

log() { printf '[%s] [%s] %s\n' "$(date -Iseconds)" "$1" "${*:2}" >&2; }
info()  { log INFO "$@"; }
warn()  { log WARN "$@"; }
error() { log ERROR "$@"; }
die()   { error "$@"; exit 1; }

Anti-Patterns

BadFix
for f in $(ls)for f in *; do or find -print0 | while read
local x=$(cmd)local x; x=$(cmd) -- preserves exit code
x=$(cmd) then an [[ -z $x ]] fallback checkx=$(cmd) || true -- under set -e a failed $() in a bare assignment aborts the script there, so the fallback never runs (opposite of the local case: local masks the failure, a bare assignment propagates it)
echo "$data"printf '%s\n' "$data"
cat file | grepgrep pat file
kill -9 $pid firstkill "$pid" first, -9 as last resort
cd dir; cmd`cd dir

Performance

  • Parameter expansion over externals: ${path%/*} not dirname, ${path##*/} not basename, ${var//old/new} not sed
  • (( )) over expr; [[ =~ ]] over echo | grep
  • Cache results: val=$(cmd) once, reuse $val
  • xargs -0 -P "$(nproc)" for parallel work
  • declare -A map for lookups instead of repeated grep

Bash 4.4+ / 5.x

  • ${var@Q} shell-quoted, ${var@U} uppercase, ${var@L} lowercase
  • declare -n ref=varname nameref for indirect access
  • wait -n wait for any background job
  • $EPOCHSECONDS, $EPOCHREALTIME -- timestamps without forking date

Linux-Specific

  • GNU coreutils differ from macOS: sed -i (no '' suffix), grep -P (PCRE support), readlink -f (canonical path)
  • timeout 30s cmd to prevent automation hangs

ShellCheck

Run shellcheck --enable=all script.sh. Key rules:

  • SC2155: Separate declaration from assignment
  • SC2086: Double-quote variables
  • SC2046: Quote command substitutions
  • SC2164: cd dir || exit
  • SC2327/SC2328: Use ${BASH_REMATCH[n]} not $n for regex captures

Pre-commit: shellcheck *.sh && shfmt -i 2 -ci -d *.sh

Verify

Run shellcheck --enable=all and shfmt -d with zero warnings before declaring done. Test edge cases: empty input, missing files, spaces in paths.

Related skills

Read and write Excel workbooks, worksheets, ranges, tables, and charts in OneDrive through Microsoft Graph with managed OAuth.

by byungkyu800 installs42 stars

More from iliaal

Browse all skills

Laravel and PHP 8.4 framework-level guidance covering architecture, Eloquent, migrations, queues, and testing, grounded in real failure modes.

by iliaal40 installs

Diagnoses software bugs through a 7-step process anchored to a failing reproduction before any fix.

by iliaal36 installs

Run spec-first code reviews with severity-ranked findings and an optional multi-agent deep mode.

by iliaal35 installs

Get an approved design document before writing a single line of code.

by iliaal33 installs

Write tests that verify real behavior—not mocks—with named scenarios, edge cases, and silent-failure coverage.

by iliaal33 installs

Tailwind CSS v4 patterns: CSS-first config, @theme tokens, component variants, and v3 migration fixes.

by iliaal31 installs