Wildcards in Unix & macOS: Why Most People Only Think They Understand Wildcards
A very useful concept on the macOS (and Linux) command line is that of wildcards. Many casual command line users will have a passing familiarity with them but most people don’t really understand how they actually work.
Free Video Course!
The content of this post is now covered in some depth on my Udemy course Learn to be a macOS Command Line Ninja. It’s a three-hour course covering everything you need to know to make you a command line pro – and it’s available free for a limited time only!
What’s a Wildcard?
Many, many years ago I remember a tutor in an introductory CS lecture telling the whole class that wildcards were nothing to do with cards. That’s patently incorrect, but probably forgivable in those pre-Google days (he didn’t look like a hardcore card player). Because just like the Joker or “wild card” in a pack of cards, a wildcard is a character that can act as a stand-in for any other character (or characters) in a file path.
Single-Character Wildcard: ?
Suppose you have a directory with a lot of similarly-named files in it:-
$ ls
appendix.txt report1.txt report3.txt report5.txt
index.txt report2.txt report4.txt
Now what if you want to do some common operation to just the report files? You can use the single wildcard character ?
to specify all the report files like this:-
$ ls report?.txt
report1.txt report2.txt report3.txt report4.txt report5.txt
What has happened here? The shell has treated the ?
character as a special character that means “match any character”. The pattern matches filenames composed of the string report
followed by another single character, followed by the string .txt
. It has then found all the files in the current directory than match this pattern and listed them using ls
.
Multi-Character Wildcard: *
Single-character wildcards are quite limiting, and in reality they’re not used terribly often. To see why, imagine what would happen when the number of report files in the example directory reached 10: the filter would no longer match because it only matches a single character, and now the number is two digits long.
Thankfully there’s another way: the *
(asterisk) wildcard. This is the workhorse of wildcards, and is the one most people know about. A version of it exists in Windows, too, but it’s nowhere near as powerful. Let’s see why…
Filtering the report files is easy with the asterisk wildcard:-
$ ls report*.txt
report1.txt report2.txt report3.txt report4.txt report5.txt
In fact, it’s easier than that:-
$ ls report*
report1.txt report2.txt report3.txt report4.txt report5.txt
Or even:-
$ ls r*
report1.txt report2.txt report3.txt report4.txt report5.txt
The magic thing about the asterisk is it tries to match any number of characters (including none). So in the final example above, anything that starts with the letter r
is enough to match – the asterisk matches all the remaining characters. A file called raptor999
would also match. The key thing to remember when using it is to ask: what is the minimum pattern that would uniquely identify what I’m looking for?
Say you wanted to make a copy of all the report files to a new subdirectory. First create a new empty directory called copies:-
$ mkdir copies
Then use the wildcard pattern r*
as an argument to cp
as follows:-
$ cp r* copies
Confirm it has copied the files to the subdirectory:-
$ ls copies
report1.txt report2.txt report3.txt report4.txt report5.txt
Why People Only Think They Understand Wildcards
Wildcards are an incredibly powerful tool, which are often misunderstood. On first meeting wildcards, many people – including experienced developers – think the wildcard is somehow sent to the command, such as ls
or cp
, which then filters the results based on it. Although that’s what happens on other operating systems (such as Windows), in Unix-based systems that is absolutely not what happens.
This can be demonstrated using the lowly echo
command. echo
simply takes the arguments you pass to it and sends them to the Terminal output:-
$ echo Hello world
Hello world
But if you pass it a wildcard, something else happens:-
$ echo r*
report1.txt report2.txt report3.txt report4.txt report5.txt
So, what’s going on here? To understand, it’s crucial to remember that the wildcard is interpreted by the shell itself, before it is sent to the command.
The shell (not the command) expands the wildcard pattern to match as many files as it can, from the current working directory. So it would expand the above command to be:-
$ echo report1.txt report2.txt report3.txt report4.txt report5.txt
Now the output makes sense. echo
is just echoing the filenames it received from the shell. Because this crucial step is never seen by the user, the exact behaviour of wildcard expansion is often misunderstood.
Pro Tip: Using echo
to Preview
echo
is a very good (and safe) way of checking a wildcard expansion before sending it to any potentially destructive commands such as mv
or rm
.
You can also use *
on its own to match all files in a directory:-
$ ls *
appendix.txt report1.txt report3.txt report5.txt
index.txt report2.txt report4.txt
Again, this might not seem any different from using ls
on its own, but the crucial thing is that the asterisk wildcard is expanded by the shell into a set of paths, so for example, if you wanted to move all the files in the directory to another directory, or delete them, or whatever, you can: *
will expand to the list of files in the current working directory, and you can pass than list to any command.
1 Response