Mastering Zsh’s Extended Globbing: Powerful Patterns, Practical Use, and Performance Trade‑offs
Extended globbing in Zsh lets you filter files by type, name, and depth with concise patterns like <code>**/*.(c|h)</code>. Learn how to enable, test, and use it safely, and understand its performance trade‑offs and portability limits.
15 Sept 2026, 17:37 UTC

Why You Should Care About Extended Globbing
When you’re working in a large codebase or a messy download folder, the classic * and ? patterns feel limited. Zsh’s extended globbing lets you filter files by type, name, size, and even time—all in a single concise expression. The feature is a game‑changer for one‑liners, scripts, and interactive sessions, but it’s shell‑specific and can bite performance if misused.
What Is Extended Globbing?
Extended globbing expands the standard glob syntax to include:
(pattern)– alternation (e.g.,(c|h)matchescorh)*@– directories only*.– regular files only*([0-9])– names that are purely numeric**– recursive descent into subdirectories
To activate the syntax you must enable the extended_glob option:
setopt extended_glob
Once enabled, a pattern such as **/*.(c|h) will recursively match all C source and header files in the current tree.
Concrete Use Cases
1. Find All C/C++ Sources in a Project
```bash
setopt extended_glob
# Recursively list .c, .cpp, and .h files
find . -type f -name "*.c" -o -name "*.cpp" -o -name "*.h"
# Equivalent Zsh one‑liner using extended globbing
print -rl -- **/*.(c|cpp|h)
```
The Zsh command is shorter and automatically respects the current working directory and any existing GLOBIGNORE settings.
2. Delete Empty Directories with a Single Pattern
```bash
setopt extended_glob
# Remove directories that contain no files or subdirectories
rmdir -p -- **/*(@)(*)
```
The pattern **/*(@)(*) matches directories (*(@)) that contain no other entries ((* )), so rmdir can safely delete them.
3. Run a Tool Only on Files Matching a Regex
```bash
setopt extended_glob
# Run a linter on files whose names start with "test_"
my_linter **/test_*.py
```
Here ** ensures the search is deep, while the prefix filter keeps the command focused.
Enabling, Testing, and Verifying
- Check your shell –
zsh --versionshould show a 5.x or later version. - Enable the feature – add
setopt extended_globto your~/.zshrcor run it interactively. - Test a simple pattern – in a directory with known files:
You should see a list of .c and .h files. If nothing appears, the option is not active.echo **/*.(c|h) - Measure performance – for a large tree:
Thetime echo **/*timeoutput gives you an idea of how long expansion takes. Typical directories finish in milliseconds; very deep or huge trees can take seconds. - Portability check – run the same script in Bash:
Bash will treat the pattern literally, producing no output or an error.bash -c 'echo **/*.(c|h)'
Performance Considerations
Extended globbing is efficient for shallow directories. The overhead comes mainly from the recursive ** operator, which walks every subdirectory. In practice:
- Small to medium trees (≤10 k files) – negligible impact.
- Large trees (hundreds of thousands of files) – expansion can take a few seconds; consider limiting depth with
**/*(/*)or usingfindfor very large scans. - Memory usage – Zsh builds a list of matches in memory; very large results may consume dozens of megabytes.
Limitations and Trade‑offs
1. Non‑portable – Scripts that rely on extended globbing will fail in Bash, Ksh, or sh unless you rewrite them or add a shebang guard.
2. Learning curve – The syntax can be intimidating at first; start with simple patterns and read the zshoptions manual.
3. Debugging difficulty – Complex glob qualifiers can produce unexpected results if you’re not careful; test in a safe environment.
Actionable Takeaway
For any Zsh user who needs to filter files by type, name, or depth, extended_glob is a powerful tool that keeps your commands short and expressive. Add setopt extended_glob to your ~/.zshrc, experiment with simple patterns, and measure performance on your own file trees. Keep an eye on portability if you share scripts; otherwise, enjoy the concise syntax that Zsh offers.
Worked Example: Clean Up Old Log Files
Suppose you want to delete all .log files older than 30 days in /var/log, but only if they’re not inside a tmp directory. Here’s a one‑liner that does that:
setopt extended_glob
# Find .log files older than 30 days, excluding tmp directories
find /var/log -type f -name "*.log" -mtime +30 -not -path "*/tmp/*" -print0 | xargs -0 rm -f
While this uses find for the date filter, the glob pattern */tmp/* illustrates how you can combine Zsh’s syntax with traditional tools. If you prefer pure Zsh, you could write:
setopt extended_glob
# Delete .log files older than 30 days, excluding tmp directories
for file in **/*.log(N/30/); do
[[ $file != */tmp/* ]] && rm -f $file
done
Here (N/30/) is a Zsh glob qualifier that matches files modified more than 30 days ago. The loop safely skips any files in tmp directories.
Summary
Extended globbing turns Zsh into a powerful file‑selection engine. Enable it with setopt extended_glob, test with echo **/*.(c|h), and leverage it for scripts that need concise, expressive patterns. Be mindful of performance on very large trees and remember that the feature is not portable to other shells.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.