# Code Reference — Bash

_29 pages_

---
# Bash (/docs/bash)



# Bash [#bash]

Shell scripting essentials.

Browse the sidebar for every cheat sheet in this section.

## Sections [#sections]

* [Examples](./examples)


---

# Arrays (/docs/bash/arrays)



# Arrays [#arrays]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Bash has indexed arrays (`declare -a`) and associative arrays (`declare -A`, bash 4+). Always expand with `"$\{arr[@]\}"` to preserve elements. Arrays are the safe alternative to storing lists in a single string.

## 🔧 Core concepts [#-core-concepts]

| Syntax           | Meaning                                |
| ---------------- | -------------------------------------- |
| `arr=(a b c)`    | Indexed array                          |
| `arr[i]=val`     | Set element                            |
| `"$\{arr[@]\}"`  | All elements (separate words)          |
| `"$\{arr[*]\}"`  | Joined with first IFS char when quoted |
| `$\{#arr[@]\}`   | Length                                 |
| `$\{!arr[@]\}`   | Indices / keys                         |
| `arr+=(x)`       | Append                                 |
| `unset 'arr[i]'` | Remove element                         |
| `declare -A map` | Associative array                      |
| `map=([k]=v)`    | Assoc init                             |

Sparse indexed arrays keep original indices after `unset`. Iterate keys with `"$\{!arr[@]\}"`.

## 💡 Examples [#-examples]

**Indexed:**

```shellscript
files=("a b.txt" "c.txt")
files+=("d.txt")
echo "${#files[@]}"
for f in "${files[@]}"; do
  echo "$f"
done
echo "${files[0]}"
```

**Slice and copy:**

```shellscript
slice=("${files[@]:1:2}")     # 2 elems from index 1
copy=("${files[@]}")
```

**Associative:**

```shellscript
declare -A ports=([web]=80 [db]=5432)
ports[cache]=6379
for k in "${!ports[@]}"; do
  echo "$k -> ${ports[$k]}"
done
```

**From command (mapfile):**

```shellscript
mapfile -t lines < file.txt
mapfile -d '' -t paths < <(find . -print0)
```

## ⚠️ Pitfalls [#️-pitfalls]

* `"$\{arr[@]\}"` vs `$\{arr[@]\}`: always quote.
* `"$\{arr[*]\}"` is one string when quoted—wrong for iterating items with spaces.
* Associative arrays need bash 4+; macOS system bash may be 3.2—use Homebrew bash or avoid.
* `arr=($string)` splits on IFS; prefer `read -a` or `mapfile`.
* `unset arr` removes whole array; `unset 'arr[0]'` one element.
* Indices are not re-packed after delete; don’t assume contiguous `0..n-1`.

## 🔗 Related [#-related]

* [variables.md](/docs/bash/variables)
* [quoting.md](/docs/bash/quoting)
* [loops.md](/docs/bash/loops)
* [string\_ops.md](/docs/bash/string-ops)
* [globbing.md](/docs/bash/globbing)


---

# Conditionals (/docs/bash/conditionals)



# Conditionals [#conditionals]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Bash branches with `if`/`elif`/`else`, `case`, and `[[ ]]` / `[ ]` / `(( ))` tests. Prefer `[[ ]]` for strings and files; `(( ))` for integers. Exit status `0` is true; non-zero is false.

## 🔧 Core concepts [#-core-concepts]

| Construct            | Use                                    |
| -------------------- | -------------------------------------- |
| `if cmd; then …; fi` | Branch on exit status                  |
| `[[ expr ]]`         | Bash test (safer, richer)              |
| `[ expr ]` / `test`  | POSIX test                             |
| `(( expr ))`         | Arithmetic; 0 → false, non-zero → true |
| `case $x in … esac`  | Pattern match                          |
| `&&` / `\|\|`        | Short-circuit                          |

**`[[ ]]` operators:** `-eq -ne -lt -le -gt -ge` (numbers as strings carefully), `=`, `==`, `!=`, `=~` regex, `-z` empty, `-n` non-empty, `-f -d -e -L -r -w -x` files, `-nt` / `-ot` newer/older.

## 💡 Examples [#-examples]

**File and string checks:**

```shellscript
if [[ -f "$config" && -r "$config" ]]; then
  source "$config"
elif [[ -z "${config:-}" ]]; then
  echo "No config" >&2
  exit 1
fi
```

**Numeric:**

```shellscript
if (( count > 10 )); then
  echo "too many"
fi

if [[ "$count" -gt 10 ]]; then …; fi   # integer compare in [[ ]]
```

**Regex and case:**

```shellscript
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@ ]]; then
  echo "looks like email"
fi

case "$1" in
  start|on)  start_svc ;;
  stop|off)  stop_svc ;;
  *)         echo "usage: $0 start|stop" >&2; exit 2 ;;
esac
```

**One-liners:**

```shellscript
[[ -d "$dir" ]] || mkdir -p "$dir"
command -v jq >/dev/null || { echo "jq required"; exit 1; }
```

## ⚠️ Pitfalls [#️-pitfalls]

* `[ $a = $b ]` breaks on empty/spaces; always quote: `[ "$a" = "$b" ]`.
* `==` inside `[ ]` is non-POSIX; use `=` or switch to `[[ ]]`.
* `(( ))` treats empty/unset as 0 with `set -u` risks—initialize first.
* `=~` right-hand side should be unquoted to be a regex; quoted = literal.
* Prefer `[[ ]]` over `[ ]` in bash scripts; keep `[ ]` for `sh` portability.
* `if cmd` tests exit code—not stdout. Capture output separately.

## 🔗 Related [#-related]

* [exit\_codes.md](/docs/bash/exit-codes)
* [loops.md](/docs/bash/loops)
* [quoting.md](/docs/bash/quoting)
* [variables.md](/docs/bash/variables)
* [debugging.md](/docs/bash/debugging)


---

# Cron (/docs/bash/cron)



# Cron [#cron]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Cron schedules recurring jobs via crontab entries. Each line is five time fields plus a command. Cron runs with a minimal environment—always set `PATH`, use absolute paths, and redirect logs. Prefer systemd timers on modern Linux when available.

## 🔧 Core concepts [#-core-concepts]

| Field  | Values          |
| ------ | --------------- |
| Minute | 0–59            |
| Hour   | 0–23            |
| Dom    | 1–31            |
| Month  | 1–12            |
| Dow    | 0–7 (0/7 = Sun) |
| `*`    | Any             |
| `*/n`  | Every n         |
| `a,b`  | List            |
| `a-b`  | Range           |

| Command        | Role                 |
| -------------- | -------------------- |
| `crontab -e`   | Edit user crontab    |
| `crontab -l`   | List                 |
| `crontab -r`   | Remove (dangerous)   |
| `/etc/cron.d/` | System drop-in files |

Special strings (Vixie): `@reboot`, `@daily`, `@hourly`, `@weekly`, `@monthly`.

## 💡 Examples [#-examples]

**User crontab entries:**

```cron
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""

# Every day at 02:30
30 2 * * * /home/ada/bin/backup.sh >>/home/ada/logs/backup.log 2>&1

# Every 15 minutes
*/15 * * * * /usr/bin/curl -fsS https://example.com/health >/dev/null

@reboot /home/ada/bin/on-boot.sh
```

**Wrapper script:**

```shellscript
#!/usr/bin/env bash
set -euo pipefail
cd "$HOME/app"
/usr/bin/flock -n /tmp/app-cron.lock ./job.sh
```

**Systemd alternative (sketch):**

```ini
# app.timer + app.service with OnCalendar=*-*-* 02:30:00
```

## ⚠️ Pitfalls [#️-pitfalls]

* Cron’s `PATH` is short—commands that work interactively may “not be found”.
* `%` in commands is special in crontab (newline)—escape as `\%`.
* No TTY; don’t assume interactive prompts or ssh-agent unless configured.
* Overlapping runs: use `flock` to prevent pile-ups.
* Timezone is the system timezone unless you set `TZ=`.
* `crontab -r` deletes without confirmation on some systems.

## 🔗 Related [#-related]

* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)
* [shebang.md](/docs/bash/shebang)
* [exit\_codes.md](/docs/bash/exit-codes)
* [debugging.md](/docs/bash/debugging)
* [ssh.md](/docs/bash/ssh)


---

# Debugging (/docs/bash/debugging)



# Debugging [#debugging]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Debug bash with tracing (`set -x`), verbose mode, ShellCheck, and strategic logging. Reproduce with the same options and environment as production. Prefer small reproducible snippets over editing large scripts blindly.

## 🔧 Core concepts [#-core-concepts]

| Tool                | Purpose                        |
| ------------------- | ------------------------------ |
| `set -x` / `set +x` | Trace expanded commands        |
| `set -v`            | Print input lines as read      |
| `PS4`               | Prefix for `set -x` traces     |
| `bash -n script`    | Syntax check only              |
| `bash -x script`    | Run with trace                 |
| `trap`              | Catch `ERR` / `DEBUG` / `EXIT` |
| ShellCheck          | Lint common bugs               |
| `printf '%q\n'`     | Show shell-escaped values      |

`ERR` trap runs when a command fails (with `set -e` nuances). `DEBUG` trap runs before each command.

## 💡 Examples [#-examples]

**Trace with context:**

```shellscript
export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
set -x
# … suspect code …
set +x
```

**ERR trap:**

```shellscript
trap 'echo "ERR at ${BASH_SOURCE}:${LINENO}: $BASH_COMMAND" >&2' ERR
set -euo pipefail
```

**Inspect values:**

```shellscript
declare -p var
printf 'args:'; printf ' <%q>' "$@"; echo
type -a cmd
command -v cmd
```

**Dry syntax + lint:**

```shellscript
bash -n ./deploy.sh
shellcheck -x ./deploy.sh
```

**Conditional debug flag:**

```shellscript
debug() { [[ "${DEBUG:-}" == 1 ]] && printf '%s\n' "$*" >&2; }
debug "port=$port"
```

## ⚠️ Pitfalls [#️-pitfalls]

* `set -x` prints secrets—disable around credential handling or redact.
* Tracing changes timing (Heisenbugs in races).
* `bash -n` does not catch runtime logic errors.
* Don’t leave `set -x` on in production cron output floods.
* Pipeline failures need `pipefail` to surface in traces meaningfully.
* Subshells complicate stack traces—note `(…)` boundaries.

## 🔗 Related [#-related]

* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)
* [exit\_codes.md](/docs/bash/exit-codes)
* [shebang.md](/docs/bash/shebang)
* [variables.md](/docs/bash/variables)
* [pipes\_redirection.md](/docs/bash/pipes-redirection)


---

# Environment Basics (/docs/bash/environment-basics)



# Environment Basics [#environment-basics]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

The **environment** is a set of key/value variables available to your shell and the programs it starts. Common ones: `PATH`, `HOME`, `USER`, `PWD`. Scripts often read `VAR=value` settings for configuration.

## 🔧 Core concepts [#-core-concepts]

| Idea                  | Meaning                                                    |
| --------------------- | ---------------------------------------------------------- |
| Environment variable  | Name → string value                                        |
| `export`              | Mark a variable for child processes                        |
| `PATH`                | Colon-separated list of directories to search for commands |
| `$VAR` / `"$\{VAR\}"` | Expand a variable                                          |
| `.env` files          | Convention for app config (not automatic in Bash)          |

Shell variables and environment variables are related; `export` promotes a shell variable into the environment.

## 💡 Examples [#-examples]

**Inspect common variables:**

```shellscript
echo "$HOME"
echo "$USER"
echo "$PWD"
echo "$PATH"
```

**Set for one command:**

```shellscript
LANG=C date
MESSAGE="hi" bash -c 'echo "$MESSAGE"'
```

**Export for the current session:**

```shellscript
export APP_ENV=development
echo "$APP_ENV"
env | grep APP_ENV
```

**Add a directory to PATH (session only):**

```shellscript
export PATH="$HOME/bin:$PATH"
which python || true
```

## ⚠️ Pitfalls [#️-pitfalls]

* Always quote expansions: `"$VAR"` — unquoted values with spaces break.
* `export FOO = bar` is wrong (spaces around `=`).
* Changing `PATH` incorrectly can make every command “not found.”
* Putting secrets in world-readable scripts or committing `.env` is unsafe.

## 🔗 Related [#-related]

* [getting\_started.md](/docs/bash/getting-started)
* [navigation.md](/docs/bash/navigation)
* [variables.md](/docs/bash/variables)
* [permissions\_basics.md](/docs/bash/permissions-basics)


---

# Examples (/docs/bash/examples)



# Examples [#examples]

Bash notes in **Examples**.


---

# Backup Script (/docs/bash/examples/backup-script)



# Backup Script [#backup-script]

*Bash · Example / how-to*

***

## 📋 Overview [#-overview]

Create a dated tar.gz backup of a directory with `set -euo pipefail`, logging, and retention cleanup.

## 🔧 Core concepts [#-core-concepts]

| Piece               | Role                |
| ------------------- | ------------------- |
| `set -euo pipefail` | Fail fast on errors |
| `tar`               | Archive + compress  |
| Date stamp          | Unique backup names |
| Retention           | Delete old archives |

## 💡 Examples [#-examples]

**backup\_script.sh:**

```shellscript
#!/usr/bin/env bash
set -euo pipefail

SRC_DIR="${1:-./data}"
DEST_DIR="${2:-./backups}"
KEEP="${KEEP:-7}"
STAMP="$(date +%Y%m%d-%H%M%S)"
NAME="backup-${STAMP}.tar.gz"

mkdir -p "$DEST_DIR"

if [[ ! -d "$SRC_DIR" ]]; then
  echo "error: source not found: $SRC_DIR" >&2
  exit 1
fi

echo "backing up $SRC_DIR -> $DEST_DIR/$NAME"
tar -czf "$DEST_DIR/$NAME" -C "$(dirname "$SRC_DIR")" "$(basename "$SRC_DIR")"

# keep newest $KEEP archives
mapfile -t OLD < <(ls -1t "$DEST_DIR"/backup-*.tar.gz 2>/dev/null | tail -n +"$((KEEP + 1))" || true)
if ((${#OLD[@]})); then
  printf 'removing %s\n' "${OLD[@]}"
  rm -f -- "${OLD[@]}"
fi

echo "done"
```

**Usage:**

```shellscript
chmod +x backup_script.sh
./backup_script.sh ./project ./backups
KEEP=14 ./backup_script.sh ./project ./backups
```

## ⚠️ Pitfalls [#️-pitfalls]

* Unquoted variables break on spaces — always quote `"$SRC_DIR"`.
* `tar` paths: `-C` + basename avoids nesting absolute paths oddly.
* Test restores periodically; a backup you cannot extract is useless.

## 🔗 Related [#-related]

* [Find and replace](/docs/bash/examples/find-and-replace)
* [Healthcheck loop](/docs/bash/examples/healthcheck-loop)


---

# Find and Replace (/docs/bash/examples/find-and-replace)



# Find and Replace [#find-and-replace]

*Bash · Example / how-to*

***

## 📋 Overview [#-overview]

Recursively find text files and apply a safe in-place string replacement with `find` + `sed` (or `perl`).

## 🔧 Core concepts [#-core-concepts]

| Piece    | Role                         |
| -------- | ---------------------------- |
| `find`   | Select files by name/type    |
| `sed -i` | In-place edit                |
| Excludes | Skip `.git` / `node_modules` |
| Dry run  | Preview before write         |

## 💡 Examples [#-examples]

**find\_and\_replace.sh:**

```shellscript
#!/usr/bin/env bash
set -euo pipefail

ROOT="${1:-.}"
OLD="${2:?usage: $0 ROOT OLD NEW}"
NEW="${3:?usage: $0 ROOT OLD NEW}"

echo "dry run matches:"
grep -RIn --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.venv \
  --include='*.md' --include='*.py' --include='*.ts' --include='*.tsx' \
  -- "$OLD" "$ROOT" || true

read -r -p "Apply replacement? [y/N] " ans
[[ "${ans:-}" =~ ^[Yy]$ ]] || exit 0

# portable-ish in-place with perl
find "$ROOT" \
  \( -path '*/.git/*' -o -path '*/node_modules/*' -o -path '*/.venv/*' \) -prune -o \
  -type f \( -name '*.md' -o -name '*.py' -o -name '*.ts' -o -name '*.tsx' \) \
  -print0 |
  xargs -0 perl -pi -e "s/\Q$OLD\E/$NEW/g"

echo "done"
```

**One-off GNU sed (Linux):**

```shellscript
find . -name '*.md' -print0 | xargs -0 sed -i 's/foo/bar/g'
```

## ⚠️ Pitfalls [#️-pitfalls]

* macOS `sed -i` needs `sed -i ''` — prefer `perl -pi` for portability.
* Binary files can be corrupted — limit by extension.
* Always dry-run with `grep` before mass edits; commit first.

## 🔗 Related [#-related]

* [Backup script](/docs/bash/examples/backup-script)
* [Healthcheck loop](/docs/bash/examples/healthcheck-loop)


---

# Healthcheck Loop (/docs/bash/examples/healthcheck-loop)



# Healthcheck Loop [#healthcheck-loop]

*Bash · Example / how-to*

***

## 📋 Overview [#-overview]

Poll an HTTP health endpoint until it succeeds or a timeout expires — useful for deploy wait scripts.

## 🔧 Core concepts [#-core-concepts]

| Piece        | Role                       |
| ------------ | -------------------------- |
| Loop + sleep | Retry with backoff gap     |
| `curl -fsS`  | Fail on HTTP errors        |
| Deadline     | Absolute timeout           |
| Exit codes   | `0` healthy, `1` timed out |

## 💡 Examples [#-examples]

**healthcheck\_loop.sh:**

```shellscript
#!/usr/bin/env bash
set -euo pipefail

URL="${1:-http://127.0.0.1:8000/health}"
TIMEOUT_SEC="${TIMEOUT_SEC:-60}"
INTERVAL_SEC="${INTERVAL_SEC:-2}"
START="$(date +%s)"

echo "waiting for $URL (timeout ${TIMEOUT_SEC}s)"

while true; do
  if curl -fsS --max-time 5 "$URL" >/dev/null; then
    echo "healthy"
    exit 0
  fi

  NOW="$(date +%s)"
  ELAPSED="$((NOW - START))"
  if ((ELAPSED >= TIMEOUT_SEC)); then
    echo "error: timed out after ${TIMEOUT_SEC}s" >&2
    exit 1
  fi

  echo "not ready (${ELAPSED}s)…"
  sleep "$INTERVAL_SEC"
done
```

**Usage:**

```shellscript
chmod +x healthcheck_loop.sh
./healthcheck_loop.sh http://127.0.0.1:9000/healthz
TIMEOUT_SEC=120 INTERVAL_SEC=3 ./healthcheck_loop.sh https://example.com/health
```

## ⚠️ Pitfalls [#️-pitfalls]

* `curl` without `-f` treats 500 as success — always use `-f` or check status.
* Infinite loops without a deadline hang CI jobs.
* DNS/TLS failures need clear stderr; avoid swallowing curl errors silently.

## 🔗 Related [#-related]

* [Backup script](/docs/bash/examples/backup-script)
* [Find and replace](/docs/bash/examples/find-and-replace)


---

# Exit Codes (/docs/bash/exit-codes)



# Exit Codes [#exit-codes]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Every command returns an 8-bit exit status: `0` success, `1–255` failure. Scripts should exit with meaningful codes. `$?` holds the last foreground status; with `pipefail`, pipelines report the rightmost non-zero (or zero if all succeed).

## 🔧 Core concepts [#-core-concepts]

| Code    | Convention                                    |
| ------- | --------------------------------------------- |
| `0`     | Success                                       |
| `1`     | General error                                 |
| `2`     | Misuse of shell builtins / bad usage (common) |
| `126`   | Found but not executable                      |
| `127`   | Command not found                             |
| `128+N` | Fatal signal N (e.g. 130 = SIGINT)            |
| `255`   | Out-of-range `exit` (wrapped)                 |

| Tool              | Role                           |
| ----------------- | ------------------------------ |
| `exit N`          | End shell/script with status N |
| `return N`        | End function with status N     |
| `$?`              | Last status                    |
| `set -e`          | Exit on failure (with caveats) |
| `set -o pipefail` | Pipeline status                |

sysexits.h codes (`EX_USAGE=64`, etc.) appear in some tools but are optional for scripts.

## 💡 Examples [#-examples]

**Check and propagate:**

```shellscript
if ! git pull; then
  echo "pull failed: $?" >&2
  exit 1
fi
```

**Meaningful script exits:**

```shellscript
usage() { echo "usage: $0 <file>" >&2; exit 2; }
[[ $# -eq 1 ]] || usage
[[ -f "$1" ]] || { echo "missing file" >&2; exit 1; }
```

**Capture without losing status:**

```shellscript
output="$(cmd)" || status=$?
status=${status:-0}
```

**Pipeline:**

```shellscript
set -o pipefail
false | true     # status 1 with pipefail; 0 without
```

## ⚠️ Pitfalls [#️-pitfalls]

* `exit` with values outside 0–255 are truncated modulo 256.
* `cmd || true` swallows failures—use only when intentional.
* `set -e` is disabled in some contexts (`if`, `&&`, `||`, `!`)—know the rules.
* Assignments: `local x=$(false)` may not trigger `set -e` as expected.
* Background jobs: `wait $pid` yields that job’s status.
* Don’t parse `$?` after another command overwrote it—save immediately.

## 🔗 Related [#-related]

* [conditionals.md](/docs/bash/conditionals)
* [pipes\_redirection.md](/docs/bash/pipes-redirection)
* [debugging.md](/docs/bash/debugging)
* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)
* [functions.md](/docs/bash/functions)


---

# Functions (/docs/bash/functions)



# Functions [#functions]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Functions group reusable commands. They share the shell’s process (unless you spawn a subshell). Arguments arrive as `$1`…`"$@"`. Prefer `local` for internals and explicit `return` status (0–255).

## 🔧 Core concepts [#-core-concepts]

| Topic        | Detail                                        |
| ------------ | --------------------------------------------- |
| Define       | `name() \{ …; \}` or `function name \{ …; \}` |
| Call         | `name arg1 arg2` (no parentheses)             |
| Args         | `$1`, `$2`, `"$@"`, `$#`                      |
| Locals       | `local var=value`                             |
| Return       | `return N` (exit status); stdout for data     |
| Scope        | Dynamic; locals shadow globals                |
| `declare -f` | List function bodies                          |

Functions cannot return structured data via `return`—print to stdout and capture with `$(...)`, or use namerefs (`declare -n`) / globals carefully.

## 💡 Examples [#-examples]

**Basic with locals:**

```shellscript
greet() {
  local who="${1:-world}"
  echo "Hello, $who"
}
greet "Ada"
```

**Return status vs output:**

```shellscript
is_dir() {
  [[ -d "$1" ]]
}

path_join() {
  local a="$1" b="$2"
  echo "${a%/}/$b"
}

if is_dir "/tmp"; then
  out="$(path_join "/var" "log")"
fi
```

**Nameref (bash 4.3+):**

```shellscript
append() {
  local -n _arr=$1
  shift
  _arr+=("$@")
}
list=()
append list a b
```

**Guard usage:**

```shellscript
usage() { echo "usage: $0 <file>" >&2; }

main() {
  [[ $# -ge 1 ]] || { usage; return 2; }
  …
}
main "$@"
```

## ⚠️ Pitfalls [#️-pitfalls]

* `local var=$(cmd)` masks `cmd`’s exit status; split assign and check.
* Forgetting `local` leaks variables into the global scope.
* `return` outside a function exits the shell when sourced carefully—use only in functions.
* Recursion depth and performance: bash functions are not optimized like compiled code.
* Defining functions in a pipeline/subshell does not affect the parent shell.
* Name clashes with external commands: prefer project prefixes (`myapp_`).

## 🔗 Related [#-related]

* [variables.md](/docs/bash/variables)
* [exit\_codes.md](/docs/bash/exit-codes)
* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)
* [subshells.md](/docs/bash/subshells)
* [getopts.md](/docs/bash/getopts)


---

# getopts (/docs/bash/getopts)



# getopts [#getopts]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`getopts` parses single-letter command-line options in a portable way. It handles clustered flags (`-abc`), optional/required arguments, and error reporting. For long options (`--verbose`), use a manual loop or external tools—not plain `getopts`.

## 🔧 Core concepts [#-core-concepts]

| Piece                    | Role                                               |
| ------------------------ | -------------------------------------------------- |
| `getopts optstring name` | Parse next option into `name`                      |
| `optstring`              | Letters; append `:` if option requires an argument |
| Leading `:` in optstring | Silent error mode (`?` / `:` in name)              |
| `$OPTARG`                | Argument value or bad option letter                |
| `$OPTIND`                | Next argv index to process                         |
| `--`                     | End of options (caller convention)                 |

After the loop, remaining operands are `"$@"` shifted with `shift $((OPTIND - 1))`.

## 💡 Examples [#-examples]

**Classic flags:**

```shellscript
#!/usr/bin/env bash
verbose=0
file=""

while getopts ':vf:' opt; do
  case "$opt" in
    v) verbose=1 ;;
    f) file="$OPTARG" ;;
    :) echo "option -$OPTARG requires an argument" >&2; exit 2 ;;
    ?) echo "unknown option -$OPTARG" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))

echo "verbose=$verbose file=$file args: $*"
```

**Usage pattern:**

```shellscript
usage() {
  cat <<'EOF' >&2
usage: tool [-v] [-f file] [args...]
EOF
  exit 2
}
```

**Reset for reuse (functions):**

```shellscript
OPTIND=1
```

## ⚠️ Pitfalls [#️-pitfalls]

* `getopts` does not parse `--long` options.
* Missing required args: with silent mode, `opt` is `:` and `OPTARG` is the option letter.
* Forgetting `shift $((OPTIND - 1))` leaves flags in `"$@"`.
* `OPTIND` is global—reset when calling `getopts` multiple times.
* Option arguments starting with `-` are still accepted as `OPTARG`.
* Prefer `getopts` over hand-rolled `$#` loops for short options.

## 🔗 Related [#-related]

* [functions.md](/docs/bash/functions)
* [variables.md](/docs/bash/variables)
* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)
* [conditionals.md](/docs/bash/conditionals)
* [exit\_codes.md](/docs/bash/exit-codes)


---

# Getting Started with Bash (/docs/bash/getting-started)



# Getting Started with Bash [#getting-started-with-bash]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Bash (Bourne Again SHell) is a command-line shell and scripting language common on Linux and macOS. You type commands interactively or save them in a script (`*.sh`) starting with a shebang.

## 🔧 Core concepts [#-core-concepts]

| Idea        | Meaning                                           |
| ----------- | ------------------------------------------------- |
| Shell       | Program that reads commands and runs programs     |
| Prompt      | Where you type (`$` often means a normal user)    |
| Command     | Program name + arguments: `ls -la`                |
| Script      | File of commands executed in order                |
| Shebang     | `#!/usr/bin/env bash` on line 1 of scripts        |
| Exit status | `0` usually means success; non-zero means failure |

On Windows you can use Git Bash, WSL, or another Unix-like environment.

## 💡 Examples [#-examples]

**Who am I / where am I:**

```shellscript
whoami
pwd
echo "Hello from Bash"
```

**Check Bash version:**

```shellscript
bash --version
echo "$BASH_VERSION"
```

**Minimal script (`hello.sh`):**

```shellscript
#!/usr/bin/env bash
set -euo pipefail
echo "Hello, World!"
```

```shellscript
chmod +x hello.sh
./hello.sh
```

**Run a one-liner without a file:**

```shellscript
bash -c 'echo hi; pwd'
```

## ⚠️ Pitfalls [#️-pitfalls]

* Spaces matter: `cd My Dir` fails; use `cd "My Dir"`.
* Your login shell might be zsh on macOS — scripts can still use Bash via shebang.
* Copying Windows paths (`C:\...`) into Bash needs `/c/...` or WSL paths.
* Forgetting `chmod +x` leads to “Permission denied” on `./script.sh`.

## 🔗 Related [#-related]

* [hello\_world.md](/docs/bash/hello-world)
* [navigation.md](/docs/bash/navigation)
* [permissions\_basics.md](/docs/bash/permissions-basics)
* [environment\_basics.md](/docs/bash/environment-basics)


---

# Globbing (/docs/bash/globbing)



# Globbing [#globbing]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Globs (pathname expansion) match filenames with `*`, `?`, and `[]` before the command runs. Unlike regex, `*` never matches `/` across directories unless globstar is on. Quote patterns when you want literals; enable `nullglob` / `failglob` for safer scripts.

## 🔧 Core concepts [#-core-concepts]

| Pattern           | Matches                               |
| ----------------- | ------------------------------------- |
| `*`               | Any string except `/`                 |
| `?`               | Single character                      |
| `[abc]` / `[a-z]` | Character class                       |
| `[!a]` / `[^a]`   | Negated class                         |
| `**`              | Recursive (needs `shopt -s globstar`) |
| `*(pat)` etc.     | Extglob patterns (`shopt -s extglob`) |

| `shopt`      | Effect                 |
| ------------ | ---------------------- |
| `nullglob`   | Unmatched glob → empty |
| `failglob`   | Unmatched glob → error |
| `dotglob`    | Include `.hidden`      |
| `nocaseglob` | Case-insensitive       |
| `globstar`   | Enable `**`            |

## 💡 Examples [#-examples]

**Safe iteration:**

```shellscript
shopt -s nullglob
for f in *.log; do
  echo "$f"
done
```

**Extglob:**

```shellscript
shopt -s extglob
rm -- !(keep|also-keep).tmp
ls *.(jpg|png|gif)
```

**Globstar:**

```shellscript
shopt -s globstar nullglob
for f in **/*.py; do
  echo "$f"
done
```

**Disable expansion:**

```shellscript
echo "*.txt"           # literal
printf '%s\n' *.txt    # expanded list
```

## ⚠️ Pitfalls [#️-pitfalls]

* Default: unmatched `*.txt` stays as the literal string `*.txt`—dangerous in `rm`.
* Globs don’t sort the way you always expect across locales; sort explicitly if needed.
* `**` without `globstar` is just two `*` tokens in some contexts—enable the option.
* Brace expansion `\{a,b\}` is not globbing; it runs even if files don’t exist.
* Leading `.` files ignored unless `dotglob` or pattern starts with `.`.
* Never parse `ls`; use globs or `find`.

## 🔗 Related [#-related]

* [quoting.md](/docs/bash/quoting)
* [loops.md](/docs/bash/loops)
* [arrays.md](/docs/bash/arrays)
* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)
* [string\_ops.md](/docs/bash/string-ops)


---

# Glossary (/docs/bash/glossary)



# Glossary [#glossary]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Alphabetical glossary of core Bash terms for shells, scripting, expansion, pipelines, and process control.

## 🔧 Core concepts [#-core-concepts]

| Term                 | Definition                                                                      |     |
| -------------------- | ------------------------------------------------------------------------------- | --- |
| Alias                | A short name that expands to a longer command string.                           |     |
| Argument             | A token passed to a command after its name.                                     |     |
| Array                | An indexed list of strings accessible as `$\{arr[i]\}`.                         |     |
| Brace expansion      | Generating multiple strings from patterns like `\{a,b\}` or `\{1..5\}`.         |     |
| Builtin              | A command implemented inside the shell, not as an external binary.              |     |
| Command substitution | Capturing a command’s output with `$(...)` or backticks.                        |     |
| Conditional          | An `if`/`[[ ]]`/`case` construct that branches on tests.                        |     |
| Exit code            | An integer status (`$?`) where `0` usually means success.                       |     |
| Expansion            | Transformations Bash applies before running a command (variables, globs, etc.). |     |
| Function             | A named compound command defined in the shell.                                  |     |
| Glob                 | A filename pattern using `*`, `?`, or `[]` that expands to matching paths.      |     |
| Here-doc             | A multiline string redirected into a command via `&lt;<EOF`.                    |     |
| IFS                  | Internal Field Separator used for word splitting and some expansions.           |     |
| Job                  | A pipeline managed by the shell’s job control (`fg`, `bg`, `jobs`).             |     |
| Loop                 | A `for`, `while`, or `until` construct that repeats commands.                   |     |
| Option               | A flag such as `-x` or `set -e` that changes shell behavior.                    |     |
| Pipe                 | Connecting one command’s stdout to another’s stdin with \`                      | \`. |
| Process substitution | Treating a command’s output as a file via `&lt;(...)` or `>(...)`.              |     |
| Quoting              | Rules (`'`, `"`, `\`) that control whether expansion occurs.                    |     |
| Redirection          | Sending stdin/stdout/stderr to files or other descriptors (`>`, `2>`, `&>`).    |     |
| Shebang              | The `#!/bin/bash` line that selects the interpreter for a script.               |     |
| Signal               | An OS notification such as `SIGINT` that processes can trap or ignore.          |     |
| Subshell             | A child shell environment created by `(...)` or some pipelines.                 |     |
| Trap                 | A handler that runs commands when a signal or exit occurs.                      |     |
| Variable             | A named string storage slot such as `NAME=value` and `$NAME`.                   |     |
| Word splitting       | Breaking unquoted expansions on `IFS` characters into multiple fields.          |     |

## 💡 Examples [#-examples]

**Variables, quoting, and exit codes:**

```shellscript
name="Ada Lovelace"
echo "Hello, $name"
grep -q pattern file.txt
echo $?
```

**Pipes and redirection:**

```shellscript
ps aux | grep nginx | awk '{print $2}' > pids.txt 2>/dev/null
```

**Arrays and globbing:**

```shellscript
files=(*.md)
echo "${#files[@]} files"
printf '%s\n' "${files[@]}"
```

## ⚠️ Pitfalls [#️-pitfalls]

* Confusing **single quotes** (literal) with **double quotes** (allow `$` and `` ` `` expansion).
* Mixing **array** indexing with string variables — unquoted `$arr` is not safe iteration.
* Treating **exit code** `0` as false in `if` — in Bash, `0` is success/true.
* Equating a **subshell** `(cd /tmp; ...)` with changing the parent shell’s directory.
* Assuming **glob** failure always errors — unmatched globs may stay literal depending on options.

## 🔗 Related [#-related]

* [variables](/docs/bash/variables)
* [quoting](/docs/bash/quoting)
* [pipes\_redirection](/docs/bash/pipes-redirection)
* [arrays](/docs/bash/arrays)
* [functions](/docs/bash/functions)
* [exit\_codes](/docs/bash/exit-codes)


---

# Hello World (/docs/bash/hello-world)



# Hello World [#hello-world]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Bash Hello World prints a line to the terminal with `echo` (or `printf`). It confirms you can run commands and, optionally, execute a script file.

## 🔧 Core concepts [#-core-concepts]

| Piece          | Role                                    |
| -------------- | --------------------------------------- |
| `echo`         | Print arguments followed by a newline   |
| `printf`       | Formatted print (more predictable)      |
| Shebang        | Selects the interpreter for scripts     |
| `./file`       | Run a file in the current directory     |
| `bash file.sh` | Run a script without the executable bit |

Prefer `printf` when you care about exact formatting; `echo` is fine for learning.

## 💡 Examples [#-examples]

**Interactive:**

```shellscript
echo "Hello, World!"
printf '%s\n' "Hello, World!"
```

**Script file:**

```shellscript
#!/usr/bin/env bash
echo "Hello, World!"
```

```shellscript
bash hello.sh
```

**With a variable:**

```shellscript
#!/usr/bin/env bash
name="Ada"
echo "Hello, ${name}!"
```

**Show command success:**

```shellscript
echo "Hello, World!"
echo "exit status was $?"
```

## ⚠️ Pitfalls [#️-pitfalls]

* `echo $name` without quotes breaks on spaces — prefer `"$name"`.
* Some `echo` flags differ across systems; `printf` is more portable.
* Running `sh hello.sh` may not be Bash if `sh` is dash.
* CRLF line endings from Windows editors can break shebang scripts.

## 🔗 Related [#-related]

* [getting\_started.md](/docs/bash/getting-started)
* [navigation.md](/docs/bash/navigation)
* [variables.md](/docs/bash/variables)
* [shebang.md](/docs/bash/shebang)


---

# Loops (/docs/bash/loops)



# Loops [#loops]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Bash offers `for`, `while`, `until`, and C-style `for (( ))`. Prefer iterating arrays or null-delimited streams over unquoted globs. Use `break` / `continue` to control flow; avoid parsing `ls`.

## 🔧 Core concepts [#-core-concepts]

| Loop                        | Typical use             |
| --------------------------- | ----------------------- |
| `for x in list; do …; done` | Fixed word list / array |
| `for ((i=0; i<n; i++)); do` | Numeric index           |
| `while cmd; do …; done`     | Until command fails     |
| `until cmd; do …; done`     | Until command succeeds  |
| `select`                    | Simple menus            |
| `break` / `continue`        | Exit / skip iteration   |
| `break n` / `continue n`    | Nested levels           |

Read lines safely with `while IFS= read -r line` (and `-d ''` for NUL). Process substitution or pipes run the loop in a subshell unless you use lastpipe / redirect.

## 💡 Examples [#-examples]

**Array and C-style:**

```shellscript
files=(*.txt)
for f in "${files[@]}"; do
  echo "file: $f"
done

for ((i = 0; i < 5; i++)); do
  echo "$i"
done
```

**Read file line by line:**

```shellscript
while IFS= read -r line || [[ -n "$line" ]]; do
  printf '%s\n' "$line"
done < "$input"
```

**Find with NUL (safe for spaces):**

```shellscript
while IFS= read -r -d '' path; do
  echo "$path"
done < <(find . -type f -print0)
```

**While with counter:**

```shellscript
n=0
while (( n < 3 )); do
  ((n++)) || true
  echo "$n"
done
```

## ⚠️ Pitfalls [#️-pitfalls]

* `for f in $(ls)` breaks on spaces/newlines—use globs or `find -print0`.
* Pipeline `cmd | while read` runs `while` in a subshell; variables won’t persist.
* Unquoted `for f in $list` word-splits; use arrays: `"$\{arr[@]\}"`.
* `for f in *.txt` leaves literal `*.txt` if no match unless `nullglob` is set.
* `((n++))` returns status 1 when expression is 0—watch with `set -e`.
* Infinite loops: ensure the condition or `break` can fire.

## 🔗 Related [#-related]

* [arrays.md](/docs/bash/arrays)
* [globbing.md](/docs/bash/globbing)
* [conditionals.md](/docs/bash/conditionals)
* [pipes\_redirection.md](/docs/bash/pipes-redirection)
* [subshells.md](/docs/bash/subshells)


---

# Navigation (/docs/bash/navigation)



# Navigation [#navigation]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Navigation means moving around the filesystem from the terminal: knowing where you are, listing contents, and changing directories. These commands are the foundation of everyday shell use.

## 🔧 Core concepts [#-core-concepts]

| Command    | Purpose                       |
| ---------- | ----------------------------- |
| `pwd`      | Print working directory       |
| `ls`       | List files and folders        |
| `cd`       | Change directory              |
| `~`        | Home directory                |
| `.` / `..` | Current / parent directory    |
| `/`        | Root of the filesystem (Unix) |

Paths can be **absolute** (`/home/sam/docs`) or **relative** (`./src`, `../other`).

## 💡 Examples [#-examples]

**Orient yourself:**

```shellscript
pwd
ls
ls -la
```

**Move around:**

```shellscript
cd ~
pwd
cd /tmp
pwd
cd -
pwd   # back to previous directory
```

**Relative paths:**

```shellscript
mkdir -p practice/sub
cd practice
pwd
cd sub
pwd
cd ..
ls
```

**Useful shortcuts:**

```shellscript
cd            # goes to $HOME
cd ~/Downloads
ls -lh        # human-readable sizes
```

## ⚠️ Pitfalls [#️-pitfalls]

* `cd` into a file fails; `cd` needs a directory.
* Unquoted spaces: `cd My Projects` → `cd "My Projects"`.
* `ls` output alone does not show hidden files — use `ls -a`.
* On Windows Git Bash, `C:` appears under `/c/` — not as `C:\`.

## 🔗 Related [#-related]

* [getting\_started.md](/docs/bash/getting-started)
* [hello\_world.md](/docs/bash/hello-world)
* [permissions\_basics.md](/docs/bash/permissions-basics)
* [environment\_basics.md](/docs/bash/environment-basics)


---

# Permissions Basics (/docs/bash/permissions-basics)



# Permissions Basics [#permissions-basics]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Unix files have permission bits controlling who can read, write, or execute them. Understanding `ls -l`, `chmod`, and ownership prevents “Permission denied” confusion.

## 🔧 Core concepts [#-core-concepts]

| Symbol               | Meaning                              |
| -------------------- | ------------------------------------ |
| `r`                  | Read                                 |
| `w`                  | Write                                |
| `x`                  | Execute (run file / enter directory) |
| User / Group / Other | Three permission triples             |
| `chmod`              | Change mode bits                     |
| `chown`              | Change owner (often needs admin)     |

Example `ls -l` mode: `-rwxr-xr--` → file; owner `rwx`, group `r-x`, others `r--`.

## 💡 Examples [#-examples]

**Inspect permissions:**

```shellscript
ls -l hello.sh
# -rw-r--r-- 1 sam sam 40 Jul 10 09:00 hello.sh
```

**Make a script executable:**

```shellscript
chmod +x hello.sh
ls -l hello.sh
./hello.sh
```

**Numeric modes (common):**

```shellscript
chmod 644 notes.txt   # rw-r--r--
chmod 755 script.sh   # rwxr-xr-x
chmod 600 secret.env  # rw-------
```

**Directories need execute to enter:**

```shellscript
mkdir private
chmod 700 private
cd private
pwd
```

## ⚠️ Pitfalls [#️-pitfalls]

* Execute bit on a directory means “traverse,” not “run like a program.”
* `chmod -R` is powerful — easy to lock yourself out of a tree.
* Scripts need both read and execute for `./script`; `bash script` only needs read.
* On Windows filesystems mounted in WSL/Git Bash, permission bits may not behave like native Linux.

## 🔗 Related [#-related]

* [getting\_started.md](/docs/bash/getting-started)
* [hello\_world.md](/docs/bash/hello-world)
* [navigation.md](/docs/bash/navigation)
* [environment\_basics.md](/docs/bash/environment-basics)


---

# Pipes & Redirection (/docs/bash/pipes-redirection)



# Pipes & Redirection [#pipes--redirection]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Redirection connects command stdin/stdout/stderr to files or other FDs. Pipes (`|`) connect stdout of one process to stdin of the next. Master these to build reliable pipelines and capture errors separately.

## 🔧 Core concepts [#-core-concepts]

| Operator           | Meaning                  |                    |
| ------------------ | ------------------------ | ------------------ |
| `>`                | Truncate/write stdout    |                    |
| `>>`               | Append stdout            |                    |
| `2>` / `2>>`       | stderr                   |                    |
| `&>` / `&>>`       | stdout+stderr (bash)     |                    |
| `>` / `2>&1`       | Merge stderr into stdout |                    |
| `&lt;`             | stdin from file          |                    |
| `&lt;<EOF`         | Here-document            |                    |
| `&lt;&lt;&lt;"$s"` | Here-string              |                    |
| `\|`               | Pipe stdout → next stdin |                    |
| \`                 | &\`                      | Pipe stdout+stderr |
| `n>&m`             | Dup FD n to m            |                    |
| `/dev/null`        | Discard                  |                    |

FD 0 stdin, 1 stdout, 2 stderr. Order matters: `cmd >out 2>&1` merges both into `out`; `cmd 2>&1 >out` leaves stderr on the terminal.

## 💡 Examples [#-examples]

**Capture and merge:**

```shellscript
make >build.log 2>&1
make 2>&1 | tee build.log
curl -fsS "$url" -o file || echo "failed" >&2
```

**Here-doc and here-string:**

```shellscript
ssh host bash <<'EOF'
  hostname
  uptime
EOF

grep -F <<<"$needle" file.txt
```

**Swap / silence:**

```shellscript
cmd 2>/dev/null
cmd >/dev/null 2>&1
cmd 3>&1 1>&2 2>&3 3>&-    # swap stdout/stderr (classic trick)
```

**Process with pipefail:**

```shellscript
set -o pipefail
curl -fsS "$url" | jq '.id'
```

## ⚠️ Pitfalls [#️-pitfalls]

* Without `pipefail`, pipeline status is the last command only—failures earlier are hidden.
* `>` overwrites silently; prefer `set -o noclobber` (`>|` to force) for safety.
* `cmd > file 2>&1` vs `cmd 2>&1 > file`—order changes which FD lands where.
* Pipes introduce subshells; side effects (vars, `cd`) don’t affect the parent.
* Here-docs without quotes expand variables; use `&lt;&lt;'EOF'` for literals.
* Large pipes buffer; for huge data consider temp files or streaming tools.

## 🔗 Related [#-related]

* [exit\_codes.md](/docs/bash/exit-codes)
* [process\_substitution.md](/docs/bash/process-substitution)
* [subshells.md](/docs/bash/subshells)
* [debugging.md](/docs/bash/debugging)
* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)


---

# Process Substitution (/docs/bash/process-substitution)



# Process Substitution [#process-substitution]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Process substitution (`&lt;(…)` and `>(…)`) exposes a command’s output or input as a temporary filename (usually `/dev/fd/N`). It lets commands that expect files work with streams—and avoids pipeline subshell issues when reading into the current shell.

## 🔧 Core concepts [#-core-concepts]

| Form             | Meaning                                       |
| ---------------- | --------------------------------------------- |
| `&lt;(cmd)`      | Read from `cmd`’s stdout as a file            |
| `>(cmd)`         | Write to a file that pipes into `cmd`’s stdin |
| Diff two streams | `diff &lt;(cmd1) &lt;(cmd2)`                  |
| vs pipe          | Pipe connects stdin; process sub gives a path |

Requires bash/zsh/ksh support and `/dev/fd` (or named temps). Not POSIX `sh`.

## 💡 Examples [#-examples]

**Compare outputs:**

```shellscript
diff -u <(sort a.txt) <(sort b.txt)
comm <(ls dir1) <(ls dir2)
```

**Read in current shell:**

```shellscript
while IFS= read -r line; do
  echo "$line"
done < <(curl -fsS "$url")
```

**Multiple inputs:**

```shellscript
paste <(cut -f1 data.tsv) <(cut -f2 data.tsv)
```

**Tee-like with >():**

```shellscript
echo hello > >(tee saved.txt)
# or
cmd | tee >(sha256sum >&2) > out.bin
```

**mapfile:**

```shellscript
mapfile -t lines < <(grep -v '^#' config)
```

## ⚠️ Pitfalls [#️-pitfalls]

* Not available in POSIX `sh` or some restricted environments.
* The path is ephemeral; don’t store it for later use after the command ends.
* `&lt;(cmd)` runs asynchronously; ensure consumers read fully.
* Combining with `sudo` may break `/dev/fd` visibility—run the whole pipeline elevated carefully.
* Prefer process substitution over `cmd >tmp && use tmp` for cleanliness, but temp files still win for huge reusable data.
* Quoting: `"&lt;(cmd)"` becomes a literal—don’t quote the substitution form.

## 🔗 Related [#-related]

* [pipes\_redirection.md](/docs/bash/pipes-redirection)
* [subshells.md](/docs/bash/subshells)
* [loops.md](/docs/bash/loops)
* [debugging.md](/docs/bash/debugging)
* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)


---

# Quoting (/docs/bash/quoting)



# Quoting [#quoting]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Quoting controls word-splitting, globbing, and expansion. Double quotes allow `$`, `` ` ``, and `\` escapes; single quotes are literal; `$''` enables ANSI-C escapes. Correct quoting prevents most bash bugs.

## 🔧 Core concepts [#-core-concepts]

| Form                | Behavior                                          |
| ------------------- | ------------------------------------------------- |
| `"..."`             | Expand `$var`, `$(...)`, `` `...` ``; keep spaces |
| `'...'`             | Fully literal (no expansions)                     |
| `$'...'`            | ANSI-C: `\n`, `\t`, `\xHH`, `\uXXXX`              |
| `$"..."`            | Locale translation (gettext)                      |
| `\`                 | Escape next character outside quotes              |
| `$'...'` vs `"..."` | Prefer `$'\n'` for real newlines in strings       |

Word-splitting uses `IFS` (default space/tab/newline) on unquoted expansions. Globbing (`*`, `?`, `[]`) also applies only when unquoted.

## 💡 Examples [#-examples]

**Preserve spaces:**

```shellscript
file="my document.txt"
rm -- "$file"          # good
# rm -- $file          # bad: two words
```

**Literal vs expanded:**

```shellscript
echo 'Home is $HOME'   # Home is $HOME
echo "Home is $HOME"   # Home is /home/you
```

**ANSI-C quotes:**

```shellscript
printf '%s\n' $'line1\nline2'
path=$'C:\\Users\\name'   # backslashes as written
```

**Nested / mixed:**

```shellscript
sql="SELECT * FROM users WHERE name='${name//\'/\'\'}'"
msg="He said \"hello\""
```

**Here-docs:**

```shellscript
cat <<'EOF'
No expansion: $HOME stays literal
EOF

cat <<EOF
Expanded: $HOME
EOF
```

## ⚠️ Pitfalls [#️-pitfalls]

* `"$@"` keeps args separate; `"$*"` joins with first IFS char.
* Empty `"$var"` is one empty argument; unquoted `$var` vanishes.
* Globs inside quotes are literal: `"*.txt"` is not a pattern.
* Backticks nest poorly; prefer `$(...)`.
* JSON/YAML in scripts: prefer single quotes or heredocs to avoid escape hell.
* `echo` + escapes is non-portable; use `printf`.

## 🔗 Related [#-related]

* [variables.md](/docs/bash/variables)
* [globbing.md](/docs/bash/globbing)
* [string\_ops.md](/docs/bash/string-ops)
* [arrays.md](/docs/bash/arrays)
* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)


---

# Scripts Best Practices (/docs/bash/scripts-best-practices)



# Scripts Best Practices [#scripts-best-practices]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Reliable bash scripts start with a clear shebang, strict mode, quoted expansions, and explicit error handling. Treat bash as glue: keep logic simple, prefer well-tested tools, and fail fast with useful messages.

## 🔧 Core concepts [#-core-concepts]

| Practice                | Why                        |
| ----------------------- | -------------------------- |
| `#!/usr/bin/env bash`   | Portable interpreter       |
| `set -euo pipefail`     | Fail on error/unset/pipe   |
| `IFS=$'\n\t'`           | Safer splitting (optional) |
| Quote `"$var"` / `"$@"` | Avoid splitting/globbing   |
| `local` in functions    | No global leaks            |
| Check deps              | `command -v`               |
| Prefer arrays           | Lists with spaces          |
| ShellCheck              | Static analysis            |

Structure: constants → functions → `main "$@"` at the bottom. Log to stderr; keep stdout for data.

## 💡 Examples [#-examples]

**Skeleton:**

```shellscript
#!/usr/bin/env bash
set -euo pipefail

log() { printf '%s\n' "$*" >&2; }
die() { log "$*"; exit 1; }

need() { command -v "$1" >/dev/null || die "missing: $1"; }

main() {
  need jq
  [[ $# -ge 1 ]] || die "usage: $0 <file>"
  local file=$1
  [[ -f "$file" ]] || die "not a file: $file"
  jq . <"$file"
}

main "$@"
```

**Safe temp files:**

```shellscript
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
```

**Dry-run pattern:**

```shellscript
run() {
  if [[ "${DRY_RUN:-}" == 1 ]]; then
    log "DRY: $*"
  else
    "$@"
  fi
}
```

## ⚠️ Pitfalls [#️-pitfalls]

* Blind `set -e` without understanding exceptions in `if` / `&&` chains.
* Parsing `ls`, `ps`, or word-splitting filenames.
* Hard-coding `/bin/bash` when `env` is more portable.
* Silent failures: always surface errors with context.
* Running as root by default—drop privileges when possible.
* Skipping ShellCheck (`shellcheck script.sh`) in CI.

## 🔗 Related [#-related]

* [shebang.md](/docs/bash/shebang)
* [debugging.md](/docs/bash/debugging)
* [exit\_codes.md](/docs/bash/exit-codes)
* [quoting.md](/docs/bash/quoting)
* [functions.md](/docs/bash/functions)
* [getopts.md](/docs/bash/getopts)


---

# Shebang (/docs/bash/shebang)



# Shebang [#shebang]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

The shebang (`#!`) is the first line of a script. It tells the kernel which interpreter to run. Prefer `#!/usr/bin/env bash` so the script finds `bash` on `PATH` across systems.

## 🔧 Core concepts [#-core-concepts]

| Form                     | Meaning                            |
| ------------------------ | ---------------------------------- |
| `#!/bin/bash`            | Hard-coded path to bash            |
| `#!/usr/bin/env bash`    | Resolve bash via `PATH` (portable) |
| `#!/bin/sh`              | POSIX `sh` (often dash on Debian)  |
| `#!/usr/bin/env python3` | Same pattern for other languages   |

Requirements: shebang must be line 1, no BOM, Unix line endings (`LF`). The file needs execute permission (`chmod +x`). Options after the interpreter (e.g. `#!/usr/bin/env bash -e`) are not portable with `env` on all systems—set options inside the script instead.

## 💡 Examples [#-examples]

**Portable bash script:**

```shellscript
#!/usr/bin/env bash
set -euo pipefail

echo "Running under: $BASH_VERSION"
```

**Make executable and run:**

```shellscript
chmod +x ./deploy.sh
./deploy.sh          # uses shebang
bash ./deploy.sh     # ignores shebang; forces bash
```

**Detect interpreter:**

```shellscript
head -n1 "$0"        # show shebang of current script
readlink -f "$(command -v bash)"
```

**Avoid:**

```shellscript
#!/usr/bin/env bash -e   # often broken: env treats "-e" as a program name
```

Use `set -e` in the body instead.

## ⚠️ Pitfalls [#️-pitfalls]

* Windows editors may save `CRLF`; the kernel then looks for `/usr/bin/env bash\r` and fails.
* `#!/bin/bash` breaks on systems where bash lives elsewhere (e.g. some BSDs, Nix).
* Running with `sh script.sh` forces POSIX `sh` and ignores bashisms even if the shebang says bash.
* `env` may not pass flags; put `set -o` / `set -euo pipefail` after the shebang.
* Scripts without a shebang inherit the caller’s shell when run as `./script` only if the kernel allows—always include one.

## 🔗 Related [#-related]

* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)
* [debugging.md](/docs/bash/debugging)
* [exit\_codes.md](/docs/bash/exit-codes)
* [variables.md](/docs/bash/variables)


---

# SSH (/docs/bash/ssh)



# SSH [#ssh]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

SSH authenticates and encrypts remote shell access. Day-to-day work uses keys, `~/.ssh/config` host aliases, agent forwarding (carefully), and non-interactive remote commands. Prefer key-based auth over passwords for scripts and automation.

## 🔧 Core concepts [#-core-concepts]

| Piece                           | Role                  |
| ------------------------------- | --------------------- |
| `ssh user@host`                 | Interactive session   |
| `ssh host 'cmd'`                | Remote command        |
| `scp` / `sftp` / `rsync -e ssh` | File transfer         |
| `ssh-keygen`                    | Create key pair       |
| `ssh-copy-id`                   | Install public key    |
| `ssh-agent` / `ssh-add`         | Unlock keys in memory |
| `~/.ssh/config`                 | Per-host settings     |
| `ProxyJump`                     | Bastion / jump host   |

Common config keys: `Host`, `HostName`, `User`, `IdentityFile`, `Port`, `ForwardAgent`, `LocalForward`, `ServerAliveInterval`.

## 💡 Examples [#-examples]

**Config aliases:**

```ssh-config
Host prod
  HostName 203.0.113.10
  User deploy
  IdentityFile ~/.ssh/id_ed25519_prod
  IdentitiesOnly yes

Host app
  HostName 10.0.0.5
  User ubuntu
  ProxyJump prod
```

**Keys and remote cmd:**

```shellscript
ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519
ssh-copy-id -i ~/.ssh/id_ed25519.pub prod
ssh prod 'hostname; uptime'
```

**Rsync and tunnels:**

```shellscript
rsync -avz -e ssh ./dist/ prod:/var/www/app/
ssh -L 5432:localhost:5432 prod   # local port forward
ssh -N -D 1080 prod               # SOCKS dynamic forward
```

**Batch / CI style:**

```shellscript
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new prod 'systemctl status app'
```

## ⚠️ Pitfalls [#️-pitfalls]

* `ForwardAgent yes` exposes your keys to the remote—use only on trusted hosts.
* Wrong file permissions: `~/.ssh` should be `700`, private keys `600`.
* Host key changes: verify MITM vs legitimate rebuild before deleting `known_hosts` entries.
* Interactive prompts break CI—use `BatchMode=yes` and deployed keys.
* Don’t embed private keys in repos; use secrets managers / SSH deploy keys.
* `scp` is dated; prefer `rsync` or `sftp` for robust transfers.

## 🔗 Related [#-related]

* [scripts\_best\_practices.md](/docs/bash/scripts-best-practices)
* [cron.md](/docs/bash/cron)
* [pipes\_redirection.md](/docs/bash/pipes-redirection)
* [debugging.md](/docs/bash/debugging)
* [exit\_codes.md](/docs/bash/exit-codes)


---

# String Operations (/docs/bash/string-ops)



# String Operations [#string-operations]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Bash can slice and transform strings with parameter expansion—no external `sed` required for common cases. Patterns use glob rules (or regex with `=~`). Prefer expansions for portability within bash; use `printf` for formatting.

## 🔧 Core concepts [#-core-concepts]

| Expansion                    | Result                         |
| ---------------------------- | ------------------------------ |
| `$\{#s\}`                    | Length                         |
| `$\{s:offset\}`              | Slice from offset              |
| `$\{s:offset:len\}`          | Slice with length              |
| `$\{s#pat\}` / `$\{s##pat\}` | Remove shortest/longest prefix |
| `$\{s%pat\}` / `$\{s%%pat\}` | Remove shortest/longest suffix |
| `$\{s/pat/rep\}`             | Replace first match            |
| `$\{s//pat/rep\}`            | Replace all                    |
| `$\{s/#pat/rep\}`            | Replace prefix                 |
| `$\{s/%pat/rep\}`            | Replace suffix                 |
| `$\{s,,\}` / `$\{s^^\}`      | Lower / upper (bash 4+)        |
| `$\{s:0:1\}`                 | First character                |

Negative offsets count from end: `$\{s: -3\}` (space required after `:`).

## 💡 Examples [#-examples]

**Paths and extensions:**

```shellscript
path="/var/log/app.tar.gz"
echo "${path##*/}"      # app.tar.gz
echo "${path%/*}"       # /var/log
echo "${path%%.*}"      # /var/log/app
echo "${path%.*}"       # /var/log/app.tar
```

**Replace and case:**

```shellscript
s="Hello World"
echo "${s// /_}"        # Hello_World
echo "${s,,}"           # hello world
echo "${s^^}"           # HELLO WORLD
```

**Default padding with printf:**

```shellscript
printf '%04d\n' 42      # 0042
printf '%s\n' "${name:-anonymous}"
```

**Trim whitespace (extglob):**

```shellscript
shopt -s extglob
x="  hi  "
x="${x##+([[:space:]])}"
x="${x%%+([[:space:]])}"
```

## ⚠️ Pitfalls [#️-pitfalls]

* Patterns are globs, not regex (`*` vs `.*`).
* `$\{s: -1\}` needs the space; `$\{s:-1\}` is a default-value expansion.
* Unicode: length/offsets are in characters for bash 4+ locales, but be careful with combining marks.
* Heavy parsing: use `awk`/`sed`/`python` for complex text.
* Quoting: `"$\{s\}"` still required when expanding results into commands.
* Associative replace with `/` in pattern: escape or choose another approach.

## 🔗 Related [#-related]

* [variables.md](/docs/bash/variables)
* [quoting.md](/docs/bash/quoting)
* [arrays.md](/docs/bash/arrays)
* [conditionals.md](/docs/bash/conditionals)
* [globbing.md](/docs/bash/globbing)


---

# Subshells (/docs/bash/subshells)



# Subshells [#subshells]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

A subshell is a child shell process. Changes to variables, `cd`, and shell options inside it do not affect the parent. Subshells appear with `(…)`, pipelines, command substitution `$(…)`, and process substitution contexts.

## 🔧 Core concepts [#-core-concepts]

| Construct               | Subshell?                                             |
| ----------------------- | ----------------------------------------------------- |
| `( cd /tmp && pwd )`    | Yes                                                   |
| `\{ cd /tmp && pwd; \}` | No (group command; needs trailing `;`)                |
| `$(cmd)` / `` `cmd` ``  | Yes                                                   |
| `cmd \| cmd`            | Each side may be (bash: last may not with `lastpipe`) |
| `coproc`                | Separate process                                      |
| Background `cmd &`      | Child process                                         |

Use subshells to isolate `cd`, temporary `umask`, or option changes. Use `\{ …; \}` when you need side effects in the current shell.

## 💡 Examples [#-examples]

**Isolate directory change:**

```shellscript
pwd
( cd /tmp && ls )
pwd   # unchanged
```

**Group vs subshell:**

```shellscript
{ echo a; echo b; } > both.txt     # current shell
( echo a; echo b ) > both.txt      # subshell (usually fine for I/O)
```

**Capture without polluting:**

```shellscript
version="$(
  cd "$repo" || exit 1
  git describe --tags
)"
```

**Avoid pipeline subshell for vars:**

```shellscript
# bad: count lost
# printf '%s\n' a b | while read -r x; do ((count++)); done

count=0
while read -r x; do ((count++)) || true; done < <(printf '%s\n' a b)
echo "$count"   # 2
```

## ⚠️ Pitfalls [#️-pitfalls]

* Variable assignments in pipelines often don’t persist—use process substitution or lastpipe.
* `(exit 1)` only exits the subshell; parent continues unless you check status.
* Nested subshells add process overhead; don’t overuse in tight loops.
* `cd` in scripts: prefer subshell or `pushd`/`popd` over relying on later `cd -`.
* Functions defined in a subshell vanish when it ends.
* `export` inside subshell does not affect parent environment.

## 🔗 Related [#-related]

* [process\_substitution.md](/docs/bash/process-substitution)
* [pipes\_redirection.md](/docs/bash/pipes-redirection)
* [variables.md](/docs/bash/variables)
* [loops.md](/docs/bash/loops)
* [functions.md](/docs/bash/functions)


---

# Variables (/docs/bash/variables)



# Variables [#variables]

*Bash · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Bash variables hold strings (and, with care, integers/arrays). Assignment has no spaces: `name=value`. Expand with `$name` or `$\{name\}`. Prefer quoting expansions to avoid word-splitting and globbing.

## 🔧 Core concepts [#-core-concepts]

| Syntax                     | Meaning                            |
| -------------------------- | ---------------------------------- |
| `var=value`                | Assign (no spaces around `=`)      |
| `$var` / `$\{var\}`        | Expand                             |
| `$\{var:-default\}`        | Default if unset or empty          |
| `$\{var:=default\}`        | Assign default if unset/empty      |
| `$\{var:?err\}`            | Error if unset/empty               |
| `$\{var:+alt\}`            | Expand `alt` if set and non-empty  |
| `readonly VAR=1`           | Immutable                          |
| `local x=1`                | Function-scoped (inside functions) |
| `export VAR=1`             | Pass to child processes            |
| `declare -i n=0`           | Integer attribute                  |
| `declare -r` / `-a` / `-A` | Readonly / indexed / associative   |

Special: `$0` script name, `$1`…`$n` args, `$#` count, `$@` / `$*` all args, `$?` last exit, `$$` PID, `$!` last background PID, `$_` last arg of previous command.

## 💡 Examples [#-examples]

**Basics and defaults:**

```shellscript
name="Ada"
echo "Hello, ${name}"
port="${PORT:-8080}"
: "${API_KEY:?API_KEY is required}"
```

**Export and env:**

```shellscript
export DATABASE_URL="postgres://localhost/app"
env | grep DATABASE
printenv DATABASE_URL
```

**Arithmetic:**

```shellscript
declare -i count=0
count+=1
((count++))
echo $((count * 2))
```

**Safe empty check:**

```shellscript
if [[ -z "${var:-}" ]]; then
  echo "unset or empty"
fi
```

## ⚠️ Pitfalls [#️-pitfalls]

* `var = value` runs command `var` (spaces break assignment).
* Unquoted `$var` splits on `IFS` and expands globs.
* `$1` vs `"$1"`: always quote unless you intentionally want splitting.
* `$*` vs `$@`: use `"$@"` to preserve argument boundaries.
* Environment variables are always strings; `(( ))` / `declare -i` for math.
* `local` only works inside functions; using it at top level errors.

## 🔗 Related [#-related]

* [quoting.md](/docs/bash/quoting)
* [arrays.md](/docs/bash/arrays)
* [string\_ops.md](/docs/bash/string-ops)
* [functions.md](/docs/bash/functions)
* [conditionals.md](/docs/bash/conditionals)

