Skip to content

wcmatch.fnmatch

from wcmatch import fnmatch

Syntax

The fnmatch library is similar to the builtin fnmatch, but with some enhancements and some differences. It is mainly used for matching filenames with glob patterns. For path names, Wildcard Match's globmatch is a more appropriate choice. Not all of the features listed below are enabled by default. See flags for more information.

Backslashes

When using backslashes, it is helpful to use raw strings. In a raw string, a single backslash is used to escape a character r'\?'. If you want to represent a literal backslash, you must use two: r'some\\path'.

Pattern Meaning
* Matches everything.
? Matches any single character.
[seq] Matches any character in seq.
[!seq] Matches any character not in seq. Will also accept character exclusions in the form of [^seq].
[[:alnum:]] POSIX style character classes inside sequences. See POSIX Character Classes for more info.
\ Escapes characters. If applied to a meta character, it will be treated as a normal character.
! When used at the start of a pattern, the pattern will be an exclusion pattern. Requires the NEGATE flag. If also using the MINUSNEGATE flag, - will be used instead of !.
?(pattern_list) The pattern matches if zero or one occurrences of any of the patterns in the pattern_list match the input string. Requires the EXTMATCH flag.
*(pattern_list) The pattern matches if zero or more occurrences of any of the patterns in the pattern_list match the input string. Requires the EXTMATCH flag.
+(pattern_list) The pattern matches if one or more occurrences of any of the patterns in the pattern_list match the input string. Requires the EXTMATCH flag.
@(pattern_list) The pattern matches if exactly one occurrence of any of the patterns in the pattern_list match the input string. Requires the EXTMATCH flag.
!(pattern_list) The pattern matches if the input string cannot be matched with any of the patterns in the pattern_list. Requires the EXTMATCH flag.
{} Bash style brace expansions. This is applied to patterns before anything else. Requires the BRACE flag.
  • Slashes are generally treated as normal characters, but on windows they will be normalized: / will become \\. There is no need to explicitly use \\ in patterns on Windows, but if you do, it will be handled. This applies to matching patterns and the filenames the patterns are applied to.
  • By default, . is not matched by *, ?, and []. See the DOTMATCH flag to match . at the start of a filename without a literal ..

POSIX Character Classes

A number of POSIX style character classes are available in the form [:alnum:]. They must be used inside sequences: [[:digit:]]. The C locale is used, and the values for each character class are found in the table below.

Property Pattern
alnum [a-zA-Z0-9]
alpha [a-zA-Z]
ascii [\x00-\x7F]
blank [ \t]
cntrl [\x00-\x1F\x7F]
digit [0-9]
graph [\x21-\x7E]
lower [a-z]
print [\x20-\x7E]
punct [!\"\#$%&'()*+,\-./:;<=>?@\[\\\]^_`{}~]
space [ \t\r\n\v\f]
upper [A-Z]
word [a-zA-Z0-9_]
xdigit [A-Fa-f0-9]

Multi-Pattern Limits

Many of the API functions allow passing in multiple patterns or using either BRACE or SPLIT to expand a pattern in to more patterns. The number of allowed patterns is limited 1000, but you can raise or lower this limit via the keyword option limit. If you set limit to 0, there will be no limit.

New 6.0

The imposed pattern limit and corresponding limit option was introduced in 6.0.

API

fnmatch.fnmatch

def fnmatch(filename, patterns, *, flags=0, limit=1000)

fnmatch takes a file name, a pattern (or list of patterns), and flags. It also allows configuring the max pattern limit. It will return a boolean indicating whether the file name was matched by the pattern(s).

>>> from wcmatch import fnmatch
>>> fnmatch.fnmatch('test.txt', r'@(*.txt|*.py)', flags=fnmatch.EXTMATCH)
True

When applying multiple patterns, a file matches if it matches any of the patterns:

>>> from wcmatch import fnmatch
>>> fnmatch.fnmatch('test.txt', [r'*.txt', r'*.py'], flags=fnmatch.EXTMATCH)
True

Exclusion patterns are allowed as well. When exclusion patterns are used in conjunction with inclusion patterns, a file will be considered matched if one of the inclusion patterns match and none of the exclusion patterns match. If an exclusion pattern is given without any inclusion patterns, the pattern will match nothing. Exclusion patterns are meant to filter other patterns, not match anything by themselves.

>>> from wcmatch import fnmatch
>>> fnmatch.fnmatch('test.py', r'*|!*.py', flags=fnmatch.NEGATE | fnamtch.SPLIT)
False
>>> fnmatch.fnmatch('test.txt', r'*|!*.py', flags=fnmatch.NEGATE | fnamtch.SPLIT)
True
>>> fnmatch.fnmatch('test.txt', [r'*.txt', r'!avoid.txt'], flags=fnmatch.NEGATE)
True
>>> fnmatch.fnmatch('avoid.txt', [r'*.txt', r'!avoid.txt'], flags=fnmatch.NEGATE)
False

As mentioned, exclusion patterns need to be applied to a inclusion pattern to work, but if it is desired, you can force exclusion patterns to assume all files should be filtered with the exclusion pattern(s) with the NEGATEALL flag. Essentially, it means if you use a pattern such as !*.md, it will assume two pattern were given: * and !*.md.

>>> from wcmatch import fnmatch
>>> fnmatch.fnmatch('test.py', r'!*.py', flags=fnmatch.NEGATE | fnamtch.NEGATEALL)
False
>>> fnmatch.fnmatch('test.txt', r'!*.py', flags=fnmatch.NEGATE | fnamtch.NEGATEALL)
True

New 6.0

limit was added in 6.0.

fnmatch.filter

def filter(filenames, patterns, *, flags=0, limit=1000):

filter takes a list of filenames, a pattern (or list of patterns), and flags. It also allows configuring the max pattern limit. It returns a list of all files that matched the pattern(s). The same logic used for fnmatch is used for filter, albeit more efficient for processing multiple files.

>>> from wcmatch import fnmatch
>>> fnmatch.filter(['a.txt', 'b.txt', 'c.py'], r'*.txt')
['a.txt', 'b.txt']

New 6.0

limit was added in 6.0.

fnmatch.translate

def translate(patterns, *, flags=0, limit=1000):

translate takes a file pattern (or list of patterns) and flags. It also allows configuring the max pattern limit. It returns two lists: one for inclusion patterns and one for exclusion patterns. The lists contain the regular expressions used for matching the given patterns. It should be noted that a file is considered matched if it matches at least one inclusion pattern and matches none of the exclusion patterns.

>>> from wcmatch import translate
>>> fnmatch.translate(r'*.{a,{b,c}}', flags=fnmatch.BRACE)
(['^(?s:(?=.).*?\\.a)$', '^(?s:(?=.).*?\\.b)$', '^(?s:(?=.).*?\\.c)$'], [])
>>> fnmatch.translate(r'**|!*.{a,{b,c}}', flags=fnmatch.BRACE | fnmatch.NEGATE | fnmatch.SPLIT)
(['^(?s:(?=.)(?![.]).*?)$'], ['^(?s:(?=.).*?\\.a)$', '^(?s:(?=.).*?\\.b)$', '^(?s:(?=.).*?\\.c)$'])

When using EXTMATCH patterns, patterns will be returned with capturing groups around the groups:

While in regex patterns like r'(a)+' would capture only the last character, even though multiple where matched, we wrap the entire group to be captured: '+(a)'r'((a)+)'.

>>> from wcmatch import fnmatch
>>> import re
>>> gpat = fnmatch.translate("@(file)+([[:digit:]])@(.*)", flags=fnmatch.EXTMATCH)
>>> pat = re.compile(gpat[0][0])
>>> pat.match('file33.test.txt').groups()
('file', '33', '.test.txt')

New 6.0

limit was added in 6.0.

New 7.1

Translate patterns now provide capturing groups for EXTMATCH groups.

fnmatch.escape

def escape(pattern):

The escape function will conservatively escape -, !, *, ?, (, ), [, ], |, {, }. and \ with backslashes, regardless of what feature is or is not enabled. It is meant to escape filenames.

>>> from wcmatch import fnmatch
>>> fnmatch.escape('**file**{}.txt')
'\\*\\*file\\*\\*\\{\\}.txt'
>>> fnmatch.fnmatch('**file**{}.txt', fnmatch.escape('**file**{}.txt'))
True

New 8.1

An escape variant for fnmatch was made available in 8.1.

fnmatch.is_magic

def is_magic(pattern, *, flags=0):
    """Check if the pattern is likely to be magic."""

This checks a given filename or pattern to see if it is "magic" or not. The check is based on the enabled features via flags. Filenames or patterns are expected to be/target full names. This variant of is_magic is meant to be run on filenames or patterns for file names only. If you need to check patterns with full paths, particularly Windows paths that include drive names or UNC sharepoints (which require special logic), it is recommended to use the glob.escape function.

>>> fnmatch.is_magic('test')
False
>>> fnmatch.is_magic('[test]ing?')
True

The table below illustrates which symbols are searched for based on the given feature. Each feature adds to the "default". In the case of NEGATE, if MINUSNEGATE is also enabled, MINUSNEGATE's symbols will be searched instead of NEGATE's symbols.

Features Symbols
Default ?*[]\
EXTMATCH ()
BRACE {}
NEGATE !
MINUSNEGATE -
SPLIT |

New 8.1

Added is_magic in 8.1.

Flags

fnmatch.CASE, fnmatch.C

CASE forces case sensitivity. CASE has higher priority than IGNORECASE.

fnmatch.IGNORECASE, fnmatch.I

IGNORECASE forces case insensitivity. CASE has higher priority than IGNORECASE.

fnmatch.RAWCHARS, fnmatch.R

RAWCHARS causes string character syntax to be parsed in raw strings: r'\u0040'r'@'. This will handle standard string escapes and Unicode including r'\N{CHAR NAME}'.

fnmatch.NEGATE, fnmatch.N

NEGATE causes patterns that start with ! to be treated as exclusion patterns. A pattern of !*.py would match any file but Python files. Exclusion patterns cannot be used by themselves though, and must be paired with a normal, inclusion pattern, either by utilizing the SPLIT flag, or providing multiple patterns in a list. Assuming the SPLIT flag, this means using it in a pattern such as inclusion|!exclusion.

If it is desired, you can force exclusion patterns, when no inclusion pattern is provided, to assume all files match unless the file matches the excluded pattern. This is done with the NEGATEALL flag.

fnmatch.NEGATEALL, fnmatch.A

NEGATEALL can force exclusion patterns, when no inclusion pattern is provided, to assume all files match unless the file matches the excluded pattern. Essentially, it means if you use a pattern such as !*.md, it will assume two patterns were given: * and !*.md, where !*.md is applied to the results of *.

Dot files will not be returned unless DOTMATCH.

fnmatch.MINUSNEGATE, fnmatch.M

When MINUSNEGATE is used with NEGATE, exclusion patterns are recognized by a pattern starting with - instead of !. This plays nice with the EXTMATCH option.

fnmatch.DOTMATCH, fnmatch.D

By default, fnmatch and related functions will not match file or directory names that start with dot . unless matched with a literal dot. DOTMATCH allows the meta characters (such as *) to match dots like any other character. Dots will not be matched in [], *, or ?.

fnmatch.EXTMATCH, fnmatch.E

EXTMATCH enables extended pattern matching. This includes special pattern lists such as +(...), *(...), ?(...), etc. See the syntax overview for more information.

EXTMATCH and NEGATE

When using EXTMATCH and NEGATE together, if a pattern starts with !(, the pattern will not be treated as a NEGATE pattern (even if !( doesn't yield a valid EXTMATCH pattern). To negate a pattern that starts with a literal (, you must escape the bracket: !\(.

fnmatch.BRACE, fnmatch.B

BRACE enables Bash style brace expansion: a{b,{c,d}}ab ac ad. Brace expansion is applied before anything else. When applied, a pattern will be expanded into multiple patterns. Each pattern will then be parsed separately. Redundant, identical patterns are discarded1 by default.

For simple patterns, it may make more sense to use EXTMATCH which will only generate a single pattern which will perform much better: @(ab|ac|ad).

Massive Expansion Risk

  1. It is important to note that each pattern is matched separately, so patterns such as {1..100} would generate one hundred patterns. Sometimes patterns like this are needed, so construct patterns thoughtfully and carefully.

  2. BRACE and SPLIT both expand patterns into multiple patterns. Using these two syntaxes simultaneously can exponential increase in duplicate patterns:

    >>> expand('test@(this{|that,|other})|*.py', BRACE | SPLIT | EXTMATCH)
    ['test@(this|that)', 'test@(this|other)', '*.py', '*.py']
    

    This effect is reduced as redundant, identical patterns are optimized away1. But it is useful to know if trying to construct efficient patterns.

fnmatch.SPLIT, fnmatch.S

SPLIT is used to take a string of multiple patterns that are delimited by | and split them into separate patterns. This is provided to help with some interfaces that might need a way to define multiple patterns in one input. It pairs really well with EXTGLOB and takes into account sequences ([]) and extended patterns (*(...)) and will not parse | within them. You can also escape the delimiters if needed: \|.

While SPLIT is not as powerful as BRACE, it's syntax is very easy to use, and when paired with EXTMATCH, it feels natural and comes a bit closer. It also much harder to create massive expansions of patterns with it, except when paired with BRACE. See BRACE and it's warnings related to pairing it with SPLIT.

>>> from wcmatch import fnmatch
>>> fnmatch.fnmatch('test.txt', r'*.txt|*.py', flags=fnmatch.SPLIT)
True
>>> fnmatch.fnmatch('test.py', r'*.txt|*.py', flags=fnmatch.SPLIT)
True

fnmatch.FORCEWIN, fnmatch.W

FORCEWIN will force Windows name and case logic to be used on Linux/Unix systems. It will also cause slashes to be normalized. This is great if you need to match Windows specific names on a Linux/Unix system.

If FORCEWIN is used along side FORCEUNIX, both will be ignored.

fnmatch.FORCEUNIX, fnmatch.U

FORCEUNIX will force Linux/Unix name and case logic to be used on Windows systems. This is great if you need to match Linux/Unix specific names on a Windows system.

When using FORCEUNIX, the names are assumed to be case sensitive, but you can use IGNORECASE to use case insensitivity.

If FORCEUNIX is used along side FORCEWIN, both will be ignored.


  1. Identical patterns are only reduced by comparing case sensitively as POSIX character classes are case sensitive: [[:alnum:]][[:ALNUM:]]


Last update: February 6, 2021