If you interact with Bash on a regular basis, chances are you’re familiar with Filename Expansion. For example:
find . -type f -name "*.md"
The *.md
pattern will expand all the filenames that match:
./README.md
./wiki/home.md
However, you may have never heard of Brace Expansion. Brace expansion falls under the umbrella of Shell Expansions, just like filename expansion.
Example 1
touch {aa,ab,ac}.txt
The result:
❯ ls -1
aa.txt
ab.txt
ac.txt
I’ve been able to use brace expansion with any command that takes a path as an argument. If I know I need to run the same command on several files and/or directories, brace expansion allows me to run that command once instead of n
times, where n
is the number of files plus directories.
Example 2
I’m in a project that has hundreds of thousands of files. I need to find numa-numa.mp4
. I know that it is either in assets/
or public/
. Given that, I can run the following:
find {assets,public} -type f -name "numa-numa.mp4"
The alternatives to using brace expansion for this example would either be using find .
or running the find
command twice: once for assets/
and once for public/
. Using find .
will search everywhere in the current directory and will eventually locate the file. However, this will be significantly slower as there are many files to look through. Running find
twice is just annoying and gets really painful when we start talking about ten directories as opposed to two.
Example 3
This example represents a real use case I have encountered in my career. I needed to copy certain files and directories from a mounted volume to another location on a server. However, I did not want to copy certain other files/directories from the volume. To accomplish this, I ran a command that ended up looking similar to:
rsync -aP /mnt/uuid/{config.json,assets/,plugins/,data/} /the/target/path/
The assets/
dir in particular was many gigabytes in size and was going to take awhile to transfer. I wanted to fire and forget, not constantly be checking back on its progress to start copying the next directory. To that end, if I had tried doing this in a
"one-liner"
before knowing about brace expansion, I probably would have written something akin to:
find /mnt/uuid -maxdepth 1 \
-name config.json -o \
-name assets -o \
-name plugins -o \
-name data \
-exec rsync -aP {} /the/target/path/ +
Not only does this require more typing (boo 👎), it also uses syntax of find
that I don’t use often, meaning I’m more likely to make a mistake.
While writing this post, I discovered that brace expansions can be nested within each other or even be combined with Pattern Matching:
chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}
I don’t fully understand what this command is doing, but it’s giving Regex so I’m excited to explore more in the future.
Until then, I’ll keep using the basics of brace expansion in my day-to-day for convenience in the spirit of laziness efficiency.