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 *.bak OR *.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 *.bak OR *.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.

  • *~pattern reads slightly more like “everything except pattern”.
  • ^pattern reads 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 find or grep pipelines 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.