Bash shell expansion

Operations in the shell very often involve repeating yourself or working with sets or ranges of information. This is where the various kinds of expansion in the shell are most useful, for generating a large number of shell tokens using a compact syntax. I’ll discuss two types here: filename expansions, which will be familiar to most shell users; and brace expansions, which seem to be in less common usage.

Filename expansions (globs)

The most commonly seen type of filename expansion in Bash is the * wildcard, which can be used to match lists of files in any directory, whether through complete listings or partial matches:

$ ls *
a.txt b.txt c.txt d.txt extra.txt README install.sh
$ ls *.txt
a.txt b.txt c.txt d.txt extra.txt

However, there are two other types of filename expansions. Firstly, you can match single characters rather than ranges of characters with a question mark:

$ ls ?.txt
a.txt b.txt c.txt d.txt

You can further restrict this by matching only a single character in a specified group of characters, or within a range:

$ ls [ac].txt
a.txt c.txt
$ ls [a-c].txt
a.txt b.txt c.txt

These can be useful in very large directories to only view files starting with a nominated letter or number, or range of letters or numbers.

Brace expansion

Brace expansion is a little different, as it generates shell tokens that don’t necessarily correspond to existing files. This allows you to expand a single general form into a lot of space-delimited specific tokens. This is probably best explained with examples. First of all, you can define the expansions you want with comma separation:

$ echo example{test,show,define,declare}
exampletest exampleshow exampledefine exampledeclare

If you’re dealing with sequences of numbers or letters, there’s also the .. separator syntax for ranges:

$ echo example{a..d}
examplea exampleb examplec exampled
$ echo example{1..5}
example1 example2 example3 example4 example5

This can be useful for creating, renaming, copying, or moving files:

$ touch file.txt.{1..6}
$ mv file.{txt,html}
$ cp file.txt{,.bak}
$ mv website.co.nz/{testing,production}/index.php

I also find it useful when dealing with long paths in Subversion branching and merging:

$ svn copy svn://server/project/{trunk,branches/experimental}
$ svn merge svn://server/project/{branches/experimental,trunk} .

You can also combine and even nest these expansions, which makes it useful for creating directory trees for new projects or chroot environments:

$ mkdir -p {test,prod}/{,usr/,usr/local/}{{,s}bin,etc,lib,share}

The above rather compact syntax creates all of the following directories:

test
test/bin
test/etc
test/sbin
test/lib
test/usr
test/usr/bin
test/usr/etc
test/usr/sbin
test/usr/lib
test/usr/share
test/usr/local
test/usr/local/bin
test/usr/local/etc
test/usr/local/sbin
test/usr/local/lib
test/usr/local/share
test/share
prod
prod/bin
prod/etc
prod/sbin
prod/lib
prod/usr
prod/usr/bin
prod/usr/etc
prod/usr/sbin
prod/usr/lib
prod/usr/share
prod/usr/local
prod/usr/local/bin
prod/usr/local/etc
prod/usr/local/sbin
prod/usr/local/lib
prod/usr/local/share
prod/share

Thanks to Reddit user silvermoot for corrections/suggestions.