Brace Expansion

Generate strings and sequences without loops — file operations, directory scaffolding, port ranges.

String Lists

Comma-separated list — generate discrete values
echo {alpha,bravo,charlie}
# alpha bravo charlie

Brace expansion is processed before any other expansion. It generates words, not files — no filesystem lookup occurs.

Specific port list — target only the ports you need
for port in {22,53,80,443,636,3268}; do
    timeout 1 bash -c "echo >/dev/tcp/10.50.1.50/$port" 2>/dev/null \
        && echo "port $port open" || echo "port $port closed"
done

Unlike ranges, comma lists let you specify exact non-sequential values. Use this for port scanning, VLAN lists, or any discrete set.

Multi-file backup — copy with extension change in one command
cp /etc/ssh/sshd_config{,.bak}

Expands to cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak. The empty first element plus the suffix generates original and modified filenames.

Rename by swapping extensions
mv report.{txt,md}

Expands to mv report.txt report.md. Clean, single-command file renaming.

Numeric Ranges

Sequential range — basic counting
echo {1..10}
# 1 2 3 4 5 6 7 8 9 10

{start..end} generates every integer in the range, inclusive on both ends.

Zero-padded range — consistent width for filenames
touch log-{01..12}.txt
# Creates log-01.txt through log-12.txt

Leading zeros in the start value force zero-padding. The width matches the widest number in the range.

Range with step — skip values
echo {0..100..10}
# 0 10 20 30 40 50 60 70 80 90 100

Third parameter is the increment. Useful for generating VLAN IDs, subnet offsets, or sample points.

Countdown — descending range
for i in {10..1}; do echo "$i"; sleep 1; done; echo "done"

When start is greater than end, the range counts down.

Alphabetic Ranges

Letter range — lowercase alphabet
echo {a..z}

Generates all 26 lowercase letters. Works with uppercase {A..Z} too.

Drive-letter style enumeration
for disk in /dev/sd{a..d}; do
    lsblk "$disk" 2>/dev/null && echo "---"
done

Combining a prefix with an alpha range produces device names, hostnames, or any letter-indexed identifiers.

Nested and Combined Expansion

Cartesian product — every combination of two lists
echo {web,db,app}-{01,02}
# web-01 web-02 db-01 db-02 app-01 app-02

Adjacent brace expansions produce the cross product. Each element from the first set pairs with every element from the second.

Three-level nesting — VLAN-aware host matrix
echo vlan{10,20}-{sw,ap}-{01..03}
# vlan10-sw-01 vlan10-sw-02 vlan10-sw-03 vlan10-ap-01 ...

No limit on depth. The shell expands left to right, multiplying at each level.

Directory tree creation — entire project skeleton in one command
mkdir -p project/{src/{main,test},docs/{api,user},config/{dev,staging,prod}}

Nested braces create a full directory hierarchy. mkdir -p handles intermediate directories. One command replaces a dozen.

Multi-file Operations

Create numbered config files from a template
for f in switch-{01..04}.conf; do
    printf "hostname %s\n" "${f%.conf}" > "$f"
done

Brace expansion generates the filenames. Parameter expansion (${f%.conf}) strips the extension for the hostname value.

Diff two config versions side by side
diff /etc/netplan/config.yaml{.bak,}

Expands to diff /etc/netplan/config.yaml.bak /etc/netplan/config.yaml. Backup first, current second — diff shows what changed.

Remove specific files by number — non-sequential
rm -f /tmp/capture-{5,6,9,10,11,13}.pcap

Comma-separated braces target exact items without globbing. No wildcard risk — only the listed files are affected.

Bulk move files into subdirectories
mv {README,LICENSE,CONTRIBUTING}.md docs/

Expands to three separate arguments to mv. Cleaner than listing each file.

Combining with Other Expansions

Brace expansion with variables — the gotcha
# This does NOT work — brace expansion happens before variable expansion
end=5
echo {1..$end}
# Literal: {1..5} is NOT produced

# Workaround: use eval (carefully) or seq
eval echo {1..$end}
seq 1 "$end"

Brace expansion is processed first in the expansion order. Variables are not yet resolved. eval forces a second pass but introduces injection risk — prefer seq in scripts.

Brace expansion with command substitution for dynamic filenames
tar czf backup-$(date +%F).tar.gz /etc/{hosts,resolv.conf,hostname}

The date substitution runs independently. Brace expansion generates the three file paths. Both expansions compose cleanly because they operate on different parts of the word.

String Generation for Testing

Generate test data — quick CSV rows
printf "host-%s,192.168.1.%s,vlan%s\n" {1..5} {101..105} {10,10,20,20,30}

printf reuses its format string for each group of arguments. Combined with brace expansion, you can generate structured test data without loops.

Generate password-like strings — character sampling
echo {a..z}{0..9}{A..Z} | tr ' ' '\n' | shuf | head -20

Cross product of letters and digits produces 26x10x26 = 6760 three-character combinations. shuf randomizes, head samples. Not cryptographically secure — use for test data only.

See Also