Skip to content

Bash Advanced

← Back to all decks

12 cards — 🟢 2 easy | 🟡 10 medium

🟢 Easy (2)

1. How do you safely handle filenames with spaces and special characters?

Show answer Always quote variables and use null-delimited processing.

# Bad — breaks on spaces:
for f in $(find . -name '*.txt'); do ...

# Good — null-delimited:
while IFS= read -r -d '' f; do
echo \"Processing: $f\"
done < <(find . -name '*.txt' -print0)

# Good — find -exec:
find . -name '*.txt' -exec wc -l {} +

# Good — globbing with nullglob:
shopt -s nullglob
for f in *.txt; do echo \"$f\"; done

Key rules:
1. Always double-quote $variables and ${expansions}
2. Use find -print0 | xargs -0 for safe piping
3. Set IFS= to prevent stripping leading/trailing whitespace
4. Use -r with read to prevent backslash interpretation

2. Explain set options: -e, -u, -o pipefail, and -x

Show answer set -euo pipefail is the 'strict mode' preamble for safe scripts.

set -e: exit on any command failure (non-zero exit code)
set -u: treat unset variables as errors (vs empty string)
set -o pipefail: pipeline fails if ANY command fails (not just last)
set -x: print each command before executing (debugging)

#!/bin/bash
set -euo pipefail

# Without pipefail:
curl bad-url | jq . # jq error hides curl failure

# With pipefail:
curl bad-url | jq . # script exits with curl's error code

Caveats of -e:
- Doesn't trigger in if/while conditions
- || and && short-circuit differently
- Use || true for commands allowed to fail
- Functions inherit -e but can be tricky with conditionals

🟡 Medium (10)

1. Explain quoting edge cases: single vs double quotes vs no quotes

Show answer Single quotes: literal, no expansion. Double quotes: variable and command expansion, no word splitting. No quotes: full expansion + word splitting + globbing.

name='world'
echo '$name' # $name (literal)
echo \"$name\" # world (expanded)
echo $name # world (expanded + word split)

Edge cases:
- Tilde: ~user expands outside quotes, not inside \"~user\"
- Arrays: \"${arr[@]}\" preserves elements, ${arr[@]} splits on spaces
- Empty vars: [ $var = yes ] fails if var is empty; [ \"$var\" = yes ] is safe

Rule: always double-quote variables unless you specifically want word splitting.

2. How do here documents and here strings work?

Show answer Here documents (<<) pass multi-line input to a command:

cat <Hello $USER
Today is $(date)
EOF

With <<'EOF' (quoted delimiter): no variable expansion (literal).
With <<-EOF: strips leading tabs (for indented scripts).

Here strings (<<<) pass a single string as stdin:

grep 'pattern' <<< \"$variable\"
read -r a b c <<< \"1 2 3\"

Here strings avoid a subshell (vs echo | grep). Use them for feeding variables to commands that read stdin. Here documents are great for generating config files, SQL queries, or multi-line messages in scripts.

3. Explain bash arithmetic: $(( )) vs (( )) vs $[ ]

Show answer $(( expr )): arithmetic expansion — evaluates and substitutes the result.
result=$((5 + 3)) # result=8
echo $((x * 2)) # inline substitution

(( expr )): arithmetic evaluation — returns exit status (0=true, 1=false).
(( count++ )) # increment
if (( x > 5 )); then echo big; fi

$[ expr ]: deprecated, same as $(( )). Do not use.

Key features:
- Supports: + - * / % ** (exponent)
- Integer only — use bc or awk for floating point
- Variables don't need $ inside: (( x = y + 1 ))
- Bitwise: & | ^ ~ << >>
- Ternary: $(( x > 5 ? 1 : 0 ))

For floating point: echo '3.14 * 2' | bc -l

4. How does getopts work for argument parsing?

Show answer getopts parses short options in a while loop.

usage() { echo \"Usage: $0 [-v] [-f file] [-n count]\" >&2; exit 1; }

verbose=0
while getopts ':vf:n:' opt; do
case $opt in
v) verbose=1 ;;
f) file=$OPTARG ;;
n) count=$OPTARG ;;
:) echo \"Option -$OPTARG requires argument\" >&2; usage ;;
\\?) echo \"Unknown option: -$OPTARG\" >&2; usage ;;
esac
done
shift $((OPTIND - 1)) # remaining args in $@

Colon after letter = requires argument. Leading colon = silent error mode. OPTARG holds the argument value. OPTIND tracks position.

For long options (--verbose), use getopt (external command) or manual parsing.

5. How do you use trap for signal handling in scripts?

Show answer trap executes a command when a signal is received.

# Cleanup on exit (any reason):
trap 'rm -f \"$tmpfile\"' EXIT

# Handle Ctrl-C:
trap 'echo \"Interrupted\"; exit 1' INT

# Ignore signals:
trap '' SIGTERM # ignore TERM

# Reset to default:
trap - INT

# Multiple signals:
trap 'cleanup' EXIT INT TERM

cleanup() {
rm -rf \"$workdir\"
echo \"Cleaned up\"
}

Common pattern:
tmpfile=$(mktemp)
trap 'rm -f \"$tmpfile\"' EXIT
# ... use tmpfile safely, auto-cleaned on any exit

Traps are inherited by subshells but reset to default in command substitutions. Use EXIT trap for guaranteed cleanup — it fires on normal exit, errors, and signals.

6. Explain process substitution with <( ) and >( )

Show answer Process substitution connects command output/input via a temporary file descriptor.

<( cmd ): cmd's stdout becomes a readable file path.
>( cmd ): provides a writable file path that feeds cmd's stdin.

# Compare outputs of two commands:
diff <(sort file1) <(sort file2)

# Feed multiple inputs to a command:
paste <(cut -f1 data.tsv) <(cut -f3 data.tsv)

# Tee to multiple destinations:
echo 'log' | tee >(gzip > file.gz) >(mail admin@example.com)

# Read output without subshell (preserves variables):
while read -r line; do
count=$((count + 1))
done < <(find . -name '*.py')

Unlike pipes, process substitution doesn't create a subshell for the reading side. Available in bash and zsh, not POSIX sh.

7. How do bash arrays work (indexed and associative)?

Show answer Indexed arrays:
arr=(one two three)
arr+=(four) # append
echo \"${arr[0]}\" # one
echo \"${arr[@]}\" # all elements
echo \"${#arr[@]}\" # count: 4
unset 'arr[1]' # remove element

Associative arrays (bash 4+):
declare -A map
map[name]=alice
map[role]=admin
echo \"${map[name]}\" # alice
echo \"${!map[@]}\" # all keys

Iteration:
for item in \"${arr[@]}\"; do echo \"$item\"; done
for key in \"${!map[@]}\"; do echo \"$key=${map[$key]}\"; done

Critical: always quote \"${arr[@]}\" to preserve elements with spaces. ${arr[*]} joins into single string. Arrays are not exported to subprocesses.

8. Explain awk basics: field processing, patterns, and actions

Show answer awk processes text line by line with pattern { action } rules.

# Print second field:
awk '{print $2}' file

# Custom delimiter:
awk -F'\\t' '{print $1, $3}' data.tsv

# Filter + compute:
awk '$3 > 100 {sum += $3; count++} END {print sum/count}' data

# Variables and printf:
awk -F: '{printf \"%-20s %s\
\", $1, $NF}' /etc/passwd

Built-in variables:
NR = line number, NF = field count
FS = field separator, RS = record separator
$0 = whole line, $1..$NF = fields

Patterns: BEGIN/END blocks, /regex/, conditional expressions.

awk is often a better choice than chained sed/grep/cut pipelines — one pass, full programming language.

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

Show answer ( commands ): runs in a subshell — variable changes don't affect parent.
{ commands; }: runs in current shell — changes persist.

# Subshell:
x=1; (x=2; echo \"inside: $x\"); echo \"outside: $x\"
# inside: 2, outside: 1

# Group:
x=1; { x=2; echo \"inside: $x\"; }; echo \"outside: $x\"
# inside: 2, outside: 2

Note: { } requires space after { and semicolon before }.

Use ( ) when you want isolation (cd, variable changes). Use { } when you want to group commands for redirection:
{ echo header; cat data.csv; } > output.txt

Pipes always create subshells: echo x | read var (var is lost). Fix with: read var < <(echo x).

10. How do you use xargs effectively?

Show answer xargs converts stdin to command arguments, enabling batching and parallelism.

# Basic:
find . -name '*.log' | xargs rm

# Safe (null-delimited for special filenames):
find . -name '*.log' -print0 | xargs -0 rm

# Limit args per invocation:
echo {1..100} | xargs -n 10 echo

# Parallel execution:
find . -name '*.gz' -print0 | xargs -0 -P 4 gunzip

# Placeholder (-I):
ls *.csv | xargs -I{} cp {} backup/{}

# Prompt before each:
ls big_files | xargs -p rm

# With stdin replacement:
cat urls.txt | xargs -P 8 -I{} curl -sO {}

xargs -P N runs N processes in parallel — simple way to parallelize without GNU parallel. Use -0 whenever possible for safety.