Skip to content

Bash Advanced

← Back to all decks

12 cards — 🟡 11 medium | 🔴 1 hard

🟡 Medium (11)

1. Explain quoting edge cases: tilde expansion and word splitting.

Show answer Tilde expansion (`~`) only works when unquoted and at the start of a word. `"~/file"` is the literal string `~/file`, NOT your home directory. Use `"$HOME/file"` inside quotes.
Word splitting occurs on unquoted variable expansions. `files="a b c"; for f in $files` iterates 3 times, but `for f in "$files"` iterates once.
Key rule: **always double-quote variable expansions** (`"$var"`) unless you intentionally want word splitting or glob expansion. Single quotes prevent ALL expansion: `'$HOME'` is the literal string `$HOME`.

2. Explain here documents (<<EOF), quoted heredocs (<<'EOF'), and here strings (<<<).

Show answer

Here document (<<EOF): sends multi-line text as stdin to a command. Variables and commands are expanded.
cat <<EOF<br>Hello $USER<br>EOF
Quoted heredoc (<<'EOF'): same but NO expansion. Treats content as literal.
cat <<'EOF'<br>Hello $USER<br>EOF # prints literal $USER
Here string (<<<): sends a single string as stdin.
grep "pattern" <<< "$variable"
Indented heredoc (<<-EOF): strips leading tabs (not spaces) for cleaner formatting in scripts.
Here docs are essential for embedding multi-line configs, SQL, or scripts within shell scripts.

3. Compare arithmetic evaluation: $(( )) vs (( )) vs let.

Show answer `$(( ))` -- arithmetic expansion, returns the result as a string. Use in assignments or command args: `x=$((a + b))`
`(( ))` -- arithmetic evaluation, returns exit status (0 for non-zero result, 1 for zero). Use in conditions: `if (( x > 5 )); then ...`
`let` -- evaluates arithmetic expressions: `let x=5+3` or `let "x = 5 + 3"`. Less common, `(( ))` is preferred.
All three support: +, -, *, /, %, ** (exponent), ++, --, ternary (?:), bitwise ops.
Avoid the deprecated `$[ ]` syntax. For floating point, use `bc` or `awk`: `echo "3.14 * 2" | bc`.

4. What is the difference between a subshell ( ) and a group command { }?

Show answer `( commands )` -- runs in a subshell (child process). Variable changes, cd, etc. do NOT affect the parent shell.
`{ commands; }` -- runs in the current shell. Variable changes persist. Note the required spaces and trailing semicolon.
Examples:
`(cd /tmp; pwd) # /tmp, but parent's cwd unchanged`
`{ cd /tmp; pwd; } # /tmp, and parent's cwd IS changed`
Use subshells for isolation (e.g., temporary env changes). Use group commands for grouping output: `{ echo "a"; echo "b"; } > file.txt`
Subshells have slight overhead from fork(). Group commands are more efficient when isolation is not needed.

5. How do you parse arguments with getopts?

Show answer `getopts` processes short options in a while loop.
`while getopts "hf:v" opt; do
case $opt in
h) usage; exit 0 ;;
f) FILE="$OPTARG" ;;
v) VERBOSE=1 ;;
?) echo "Invalid option"; exit 1 ;;
esac
done
shift $((OPTIND - 1)) # remaining args in $@`
A colon after a letter means it takes an argument (`f:` means `-f value`). Leading colon in optstring (`:hf:v`) enables silent error handling.
`getopts` handles combined flags (`-vf file`) and is POSIX-portable. For long options (`--file`), use `getopt` (GNU) or manual parsing.

6. How does trap work for signal handling in bash?

Show answer `trap 'commands' SIGNAL` executes commands when the shell receives SIGNAL.
`trap 'rm -f "$tmpfile"; exit' INT TERM EXIT`
Common signals: INT (Ctrl-C), TERM (kill default), EXIT (always runs on exit), ERR (on command failure), DEBUG (before every command).
`trap '' INT` -- ignore SIGINT
`trap - INT` -- reset to default behavior
`trap 'echo cleanup' EXIT` -- cleanup on script exit (any reason)
Best practice: use trap EXIT for cleanup to handle both normal exit and signals. Combine with temporary file patterns:
`tmpfile=$(mktemp); trap 'rm -f "$tmpfile"' EXIT`

7. What are the essential awk field processing patterns?

Show answer awk splits each line into fields ($1, $2, ...) with $0 being the whole line.
Basic patterns:
- Print column: `awk '{print $2}' file`
- Custom delimiter: `awk -F: '{print $1}' /etc/passwd`
- Filter: `awk '$3 > 100 {print $1, $3}' file`
- Sum a column: `awk '{sum += $2} END {print sum}' file`
- Count lines: `awk 'END {print NR}' file`
- Pattern match: `awk '/error/ {print}' log`
- Multiple delimiters: `awk -F'[,;]' '{print $1}'`
Built-in variables: NR (line number), NF (field count), FS (field separator), OFS (output field separator).
`awk -v OFS="\t" '{print $1, $3}'` -- set output delimiter.

8. How does process substitution work with <() and >()?

Show answer Process substitution lets you use command output as a file argument.
`<(command)` -- creates a temporary file-like object containing command's stdout.
`diff <(sort file1) <(sort file2)` -- compare sorted versions without temp files.
`>(command)` -- creates a file-like object that feeds into command's stdin.
`tee >(gzip > file.gz) >(wc -l) > /dev/null` -- compress and count simultaneously.
Key differences from pipes: process substitution provides a filename (not stdin), so it works with commands that require file arguments. It uses /dev/fd/ or named pipes internally.
Available in bash and zsh, NOT in POSIX sh.

9. Explain "set -euo pipefail" and why it matters.

Show answer `set -e` -- exit immediately on any command failure (non-zero exit code)
`set -u` -- treat unset variables as errors (prevents typos like `rm -rf $UNSET_VAR/`)
`set -o pipefail` -- a pipeline fails if ANY command fails (not just the last one)
Together they make scripts fail loudly instead of silently continuing with errors.
Common pattern: `#!/usr/bin/env bash
set -euo pipefail`
Caveats: `set -e` does NOT trigger in if/while conditions or commands with `||`/`&&`. Use `|| true` to explicitly allow failure. Some scripts add `set -x` for debug tracing.

10. How do array operations work in bash (declare, append, iterate, associative)?

Show answer **Indexed arrays**:
`arr=(a b c)` or `declare -a arr`
`arr+=(d)` -- append
`echo ${arr[0]}` -- access element
`echo ${arr[@]}` -- all elements
`echo ${#arr[@]}` -- length
`for item in "${arr[@]}"; do echo "$item"; done` -- iterate (quote to preserve spaces)
**Associative arrays** (bash 4+):
`declare -A map
map[key1]=value1
map[key2]=value2
for k in "${!map[@]}"; do echo "$k: ${map[$k]}"; done`
`${!arr[@]}` gives indices/keys. Always quote `"${arr[@]}"` to handle elements with spaces. Unset with `unset arr[2]` or `unset arr`.

11. How do exec and advanced redirections work in bash?

Show answer `exec` without a command replaces the current shell's file descriptors.
- `exec 3>file` -- open fd 3 for writing
- `exec 3- `exec 3>&-` -- close fd 3
- `exec > >(tee log.txt)` -- redirect all subsequent stdout to tee
Advanced redirections:
- `2>&1` -- redirect stderr to stdout
- `&>file` -- redirect both stdout and stderr to file
- `command 2>&1 | tee log` -- capture both streams in a pipe
- `{ cmd1; cmd2; } 2>&1 | logger` -- group and redirect
`exec` with a command replaces the shell process entirely: `exec /usr/bin/app` -- the shell is gone, replaced by app.

🔴 Hard (1)

1. What are advanced bash parameter expansion forms?

Show answer Beyond `$var` and `${var}`, bash supports:
- `${var:-default}` -- use default if var is unset or empty
- `${var:=default}` -- assign default if unset or empty
- `${var:+alternate}` -- use alternate if var IS set
- `${var:?error msg}` -- exit with error if unset
- `${var#pattern}` -- remove shortest prefix match
- `${var##pattern}` -- remove longest prefix match
- `${var%pattern}` -- remove shortest suffix match
- `${var%%pattern}` -- remove longest suffix match
- `${var/old/new}` -- replace first occurrence
- `${var//old/new}` -- replace all occurrences
- `${#var}` -- string length
Example: `path="/usr/local/bin/app"; echo ${path##*/}` prints `app` (basename).