Skip to content
Linux Basics: Generate Small Lists with Bash Brace Expansion

Linux Basics: Generate Small Lists with Bash Brace Expansion

October 21, 2026

Typing several related names is easier when their shared parts appear only once. Bash brace expansion generates short lists of text, including combinations and numeric sequences. It is useful for previewing names or supplying a known set of arguments, but its expansion order places important limits on variable-driven ranges.

These examples were tested on Linux with GNU Bash 5.2.37. Use Bash rather than sh, which does not promise this syntax. Nothing here creates the generated filenames; the scripts only print their arguments.

Prepare a scratch directory

You need Bash, a text editor, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. Save each named source file in this directory before running it. The guards stop the shell if setup fails; invoking bash explicitly avoids changing executable permissions. No administrator access is needed.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Keep a shared prefix and suffix

Save this as brace-names.sh:

#!/usr/bin/env bash
set -B
printf 'Name: <%s>\n' "daily notes-"{draft,final}.txt
printf 'Chosen order: %s\n' color-{blue,red,green}

Run it:

bash brace-names.sh

Output:

Name: <daily notes-draft.txt>
Name: <daily notes-final.txt>
Chosen order: color-blue
Chosen order: color-red
Chosen order: color-green

The first expression produces two arguments. Its quoted prefix preserves the space in daily notes-, while the unquoted braces and comma still perform expansion. The .txt suffix is attached to both results.

Brace lists preserve the order you write; they are not alphabetically sorted. That is why blue, red, and green appear in that order. set -B enables brace expansion explicitly, although it is normally enabled in Bash.

Do not add unquoted spaces around the commas. Those spaces divide shell words before this mechanism can build the list. Quote an individual item when its text needs spaces, rather than quoting the entire brace expression.

2. Build combinations and padded sequences

Save this as brace-combinations.sh:

#!/usr/bin/env bash
set -B
printf 'Target: %s\n' {api,web}-{dev,prod}
printf 'Batch: %s\n' batch-{01..05..2}

Run it:

bash brace-combinations.sh

Output:

Target: api-dev
Target: api-prod
Target: web-dev
Target: web-prod
Batch: batch-01
Batch: batch-03
Batch: batch-05

Adjacent lists generate combinations. Each first-list value is paired with each second-list value, giving four target names here. This is handy for a small, fixed test matrix or a predictable collection of output labels.

The numeric sequence starts at one, advances by two, and includes five. The leading zero on an endpoint asks Bash to pad every generated number to the same width. These are strings used as arguments; zero padding does not make this example perform octal arithmetic.

The increment is optional, so {1..3} produces one, two, and three. A descending range such as {3..1} works too. Avoid enormous ranges or many adjacent lists: Bash constructs the expanded arguments before the receiving command starts.

3. Use a loop when the endpoint is a variable

Save this as brace-boundaries.sh:

#!/usr/bin/env bash
set -B
end=3
printf 'Variable endpoint: <%s>\n' {1..$end}
printf 'Quoted expression: <%s>\n' '{1..3}'
for ((number = 1; number <= end; number++)); do
  printf 'Loop value: %d\n' "$number"
done

Run it:

bash brace-boundaries.sh

Output:

Variable endpoint: <{1..3}>
Quoted expression: <{1..3}>
Loop value: 1
Loop value: 2
Loop value: 3

Brace expansion happens before parameter expansion. At the brace-expansion stage, $end is not an integer endpoint, so that expression remains unchanged. Later expansion replaces the variable with 3, but Bash does not go back and perform brace expansion again. The result is one literal argument containing {1..3}.

The fully quoted expression is also one literal argument, for a different reason: quoting disables its special interpretation. The arithmetic loop is the straightforward alternative when a bound is already a validated integer. Here the bound is a fixed assignment; validate and limit values before accepting them from users. Do not use eval to force another expansion pass over untrusted input.

Keep in mind

Unlike filename globbing, brace expansion does not check whether a name exists. A malformed brace expression may remain literal rather than report an error. Preview generated arguments with printf before using them with file-changing commands. Also distinguish expansion such as {a,b} from a command group such as { printf 'hello\n'; }: the latter runs commands and requires its own shell syntax.

Clean up

In the same terminal, remove only the named practice files, return to your original directory, and remove the empty scratch directory:

rm -- brace-names.sh brace-combinations.sh brace-boundaries.sh
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

References

Acknowledgements

This post was created with assistance from AI.

Last updated on