When operating files in Linux, the most common actions are viewing, copying, moving, and deleting. Behind these commands, a low-key yet ubiquitous helper is the wildcard.
However, sometimes the filenames to match are too numerous or complex, and wildcards fall short. At this point, regular expressions come into play.
In this article, we will clarify the differences and connections between these two, as well as when to use each.
1. Wildcards: The Basics of File Matching
Wildcards are the most commonly used method for file matching in Linux.
Commands like <span><span>ls</span></span>, <span><span>cp</span></span>, <span><span>mv</span></span>, <span><span>rm</span></span>, and <span><span>tar</span></span> do not actually recognize wildcards themselves; instead, the Shell expands the wildcards into actual filenames before executing the command.
For example, if you input:
ls *.log
In reality, the Shell transforms it into:
ls access.log error.log system.log
This means that the function of the wildcard is completed before the command is executed.
Sometimes you may encounter a situation where executing ls *.log results in a list of files that do not end with .log.
Don’t panic; this is usually because the nullglob option was inadvertently enabled.
Simply execute the following command to restore normalcy:
shopt -u nullglob
2. Common Wildcard Patterns
There are several commonly used patterns:
-
<span><span>*</span></span>: Matches any character of any length (excluding<span><span>/</span></span>). Example:<span><span>*.txt</span></span>matches all files ending with<span><span>.txt</span></span>. -
<span><span>?</span></span>: Matches any single character. Example:<span><span>?.sh</span></span>matches<span><span>a.sh</span></span>,<span><span>b.sh</span></span>, etc., but does not match<span><span>ab.sh</span></span>. -
<span><span>[abc]</span></span>: Matches any one character within the brackets. Example:<span><span>[abc].log</span></span>matches<span><span>a.log</span></span>,<span><span>b.log</span></span>, and<span><span>c.log</span></span>. -
<span><span>[!abc]</span></span>or<span><span>[^abc]</span></span>: Matches characters not in the brackets. Example:<span><span>[!0-9]*</span></span>matches files that do not start with a digit. -
<span><span>[0-9]</span></span>: Matches characters within a specified range. Example:<span><span>file[0-9].txt</span></span>matches<span><span>file0.txt</span></span>to<span><span>file9.txt</span></span>.
These patterns are sufficient for most daily scenarios, such as batch deleting logs, moving scripts, and copying files of the same type.
3. Extended Wildcards: More Flexible Matching
If you enable extended wildcards with <span><span>shopt -s extglob</span></span>, you can use more powerful patterns.
Here are a few examples:
-
<span><span>@(foo|bar)</span></span>: Matches<span><span>foo</span></span>or<span><span>bar</span></span>. Example:<span><span>@(foo|bar).txt</span></span>matches<span><span>foo.txt</span></span>and<span><span>bar.txt</span></span>. -
<span><span>*(pattern)</span></span>: Matches zero or more repetitions. Example:<span><span>*(test)</span></span>can match an empty string,<span><span>test</span></span>, or<span><span>testtest</span></span>. -
<span><span>+(pattern)</span></span>: Matches one or more repetitions. Example:<span><span>+(foo|bar)</span></span>matches<span><span>foofoo</span></span>,<span><span>barfoo</span></span>, etc. -
<span><span>?(pattern)</span></span>: Matches zero or one. Example:<span><span>?(debug).log</span></span>matches<span><span>app.log</span></span>or<span><span>appdebug.log</span></span>. -
<span><span>!(pattern)</span></span>: Matches strings that do not conform to any pattern in the brackets. Example:<span><span>!(tmp|bak)</span></span>matches all files except<span><span>tmp</span></span>and<span><span>bak</span></span>.
This syntax may look a bit like regular expressions, but it is still parsed by the Shell itself.
It works especially well for files with regular naming conventions, such as grouped logs or versioned files.
4. Path-Level Matching: The Recursive Power of globstar
Furthermore, you can enable the ** recursive wildcard with shopt -s globstar:
ls **/*.sh
This will recursively match all .sh files in the current directory and all subdirectories.
This is very suitable for managing large projects or multi-level file structures.
5. When Filename Rules Are Too Complex, Use Regular Expressions
Wildcards are simple and efficient, but they have inherent limitations:
-
They can only match “filenames” and do not cross directories by default (unless using
<span><span>**</span></span>); -
They have limited expressiveness, making them unsuitable for complex naming patterns like dates.
At this point, it is time to use regular expressions.
The find command can use both wildcards (<span><span>-name</span></span>) and regular expressions (<span><span>-regex</span></span>).
Practical Uses of <span><span>find</span></span>:
1. Use wildcards to match filenames (most common)
find . -name "*.log"
2. Use regular expressions to match complex filenames
find . -regex '.*/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\.log'
This matches logs in the format of 2025-10-31.log. However, we would prefer to write [0-9][0-9][0-9][0-9] as [0-9]{4}, which requires using the -regextype posix-extended option. A better solution is provided at the end of this article.
By default, find uses Emacs regular syntax, not the POSIX ERE (Extended Regular Expressions) we are familiar with.
Note: The -regex option in find matches the complete path starting from the root directory, including directory names. Therefore, the preceding .* is usually necessary, while whether to include / depends on whether you want to match the complete filename.
3. Case-insensitive matching
find /var/log -iregex '.*\(error\|warn\)\.log'
4. Combine with exec for further actions
find . -regex '.*\.bak$' -exec rm -f {} \;
6. Wildcards or Regular Expressions? How to Choose the Right One
-
If you are just doing simple matching in the current directory, wildcards are the fastest.
-
For recursive file searches, use
<span><span>find -name</span></span>. -
If the filename pattern is too complex (like dates or version numbers), use
<span><span>find -regex</span></span>. -
To search for file contents, use
<span><span>grep -E</span></span>.
In summary:
Wildcards quickly match filenames, while regular expressions match complex patterns; wildcards are expanded by the Shell, while regular expressions are parsed by commands.
7. Conclusion
-
Wildcards are the matching language of the Shell;
-
Extended wildcards (extglob) make it more powerful;
-
<span><span>globstar</span></span>enables recursion; -
Regular expressions are more complex matching tools, best used in conjunction with
<span><span>find</span></span>and<span><span>grep</span></span><code><span><span> for maximum power.</span></span>
đĄ Bonus: Making <span><span>find</span></span> Regular Expressions More Convenient
As mentioned earlier, to allow <span><span>find</span></span><span><span> to support regex patterns like </span></span><code><span><span>{2}</span></span> and <span><span>+</span></span>, you need to add the -regextype posix-extended option.
The problem is that typing this long string every time can be cumbersome.
A more convenient approach is to write a small function that automatically adds it for you (note: after copying the code, you may need to replace whitespace characters with spaces to run it):
efind() { # Save the original parameter array args=("$@")
# Find the position of -regex or -iregex idx=-1 for i in "{!args[@]}"; do if [[ "${args[$i]}" == "-regex" || "${args[$i]}" == "-iregex" ]]; then idx=$i break fi done
if [ $idx -ge 0 ]; then # Insert -regextype posix-extended before -regex/-iregex prefix=( "${args[@]:0:$idx}" ) suffix=( "${args[@]:$idx}" ) args=( "${prefix[@]}" -regextype posix-extended "${suffix[@]}" ) fi
# Call the system's find (to avoid recursive calls to the function itself) command find "${args[@]}"}
Add this function to your toolbox, and you can use it like this:
efind . -regex '.*/[0-9]{4}-[0-9]{2}-[0-9]{2}\.log'
This function will automatically insert -regextype posix-extended in the appropriate place, making it both safe and convenient.
Recommended Reading
The Art of Date Handling in Shell: Build Your Own Toolbox
A Guide to Avoiding Pitfalls in Linux bc Base Conversion
Shell Arrays: Easily Manage Data Collections