Working on the command line often involves manipulating groups of
files. While basic wildcards like * are useful for including
files, what happens when you need to perform an action on almost
everything in a directory, excluding just a few specific types?
Manually selecting files or piping ls through grep -v can be
cumbersome.
Fortunately, Zsh (the Z shell) offers powerful “globbing” (filename
generation) features that make excluding files elegant and efficient.
Today, we’ll focus on two incredibly useful operators available
under Zsh’s EXTENDED_GLOB option: the exclusion operator (~)
and the negation operator (^).
Prerequisite: Enabling EXTENDED_GLOB
Both ~ and ^ require the EXTENDED_GLOB option to be enabled
in Zsh. You can enable it for your current session like this:
setopt EXTENDED_GLOB
To make this permanent, add that line to your Zsh configuration
file, typically ~/.zshrc:
# ~/.zshrc
setopt EXTENDED_GLOB
Now, let’s dive into the operators.
Method 1: The Exclusion Operator (~)
Think of the ~ operator as meaning “match the pattern on the left,
but not if it also matches the pattern on the right.” It’s an
intuitive way to subtract specific patterns from a broader match.
Syntax: pattern_to_match ~ pattern_to_exclude
Common Use Case: Match everything (*), but exclude specific extensions.
Example: Excluding .bak files
Imagine a directory with source files, compiled files, and backup
(.bak) files. To list everything except the backups:
# Ensure EXTENDED_GLOB is set
setopt EXTENDED_GLOB
# List all files except those ending in .bak
ls *~*.bak
*: Matches all files initially.~: Excludes anything matching the following pattern.*.bak: Matches files ending with.bak.
Example: Excluding Multiple Extensions (.bak and .zip)
Need to exclude more than one type? Use parentheses () to group
the patterns-to-exclude, separated by the pipe | (OR) symbol.
# List all files except those ending in .bak OR .zip
ls *~(*.bak|*.zip)
*: Matches all files.~: Excludes…(*.bak|*.zip): …anything matching*.bakOR*.zip.
Method 2: The Negation Operator (^)
The ^ operator is more direct: it matches anything that does
not conform to the pattern following it.
Syntax: ^pattern_to_avoid
Common Use Case: Match any file that doesn’t fit a specific pattern.
Example: Excluding .bak files
Using ^, we achieve the same result as with ~ for single exclusions:
# Ensure EXTENDED_GLOB is set
setopt EXTENDED_GLOB
# List all files that DO NOT end in .bak
ls ^*.bak
^: Match files that do not match the following pattern.*.bak: Files ending with.bak.
Example: Excluding Multiple Extensions (.bak and .zip)
Similarly, use () and | to negate multiple patterns at once.
# List all files that DO NOT end in .bak OR .zip
ls ^(*.bak|*.zip)
^: Match files that do not match…(*.bak|*.zip): …the pattern*.bakOR*.zip.
Choosing Between ~ and ^
For the common task of excluding specific extensions from a full
directory listing (*), both *~pattern and ^pattern often yield
the same results.
*~patternreads slightly more like “everything except pattern”.^patternreads more like “not pattern”.
The choice often comes down to personal preference and which feels more logical for the specific task at hand.
What About Bash?
This is a crucial point: The ~ and ^ globbing operators, as
described here, are specific to Zsh. They are not available in
Bash by default.
Bash does have its own extended globbing capabilities, enabled
via shopt -s extglob. The closest equivalent in Bash for exclusion
is the !(pattern-list) syntax.
Bash Example (Equivalent to ls ^(*.bak|*.zip)):
# Enable extended globbing in Bash
shopt -s extglob
# List files not ending in .bak or .zip
ls !(*.bak|*.zip)
# Remember to disable if needed: shopt -u extglob
While functional, many find Zsh’s ~ and ^ syntax slightly cleaner
and more readable for these exclusion tasks.
Practical Use Cases
Mastering these operators streamlines many command-line tasks:
- Cleaning Directories:
rm *~(*.log|*.tmp)- Remove everything except log and temp files. - Archiving Projects:
tar czf project.tar.gz *~(*.o|*.bak|*.zip)- Archive source files, excluding object files, backups, and other archives. - File Processing:
process_script ^(*.txt|*.md)- Run a script on all files that aren’t text or markdown. - Scripting: Make shell scripts more robust and less reliant on complex
findorgreppipelines for simple exclusions.
Conclusion
Zsh’s EXTENDED_GLOB feature, particularly the ~ (exclusion) and
^ (negation) operators, provide a powerful and elegant way to
select files while excluding specific patterns. By understanding
and utilizing these operators, you can make your command-line work
faster, your commands clearer, and your scripts more efficient.
While Bash offers alternatives via extglob, Zsh’s syntax often
excels for its readability in exclusion scenarios.