5

Consider this pattern: *.py. It matches all the paths ending with the py extension. Now, is it possible to find a pattern which matches everything else?

I thought this would do it: *[!.][!p][!y], but apparently fnmatch.fnmatch always returns False with this.

I'd like to avoid regexes. I know I could use them, but in this case it isn't possible.

fish2000
  • 4,289
  • 2
  • 37
  • 76
rubik
  • 8,814
  • 9
  • 58
  • 88
  • FYI, `fnmatch` does **not** always return `False` for your proposed pattern: `fnmatch.fnmatch("blah","*[!.][!p][!y]")` returns `True`. The behavior you're probably observing is that `*[!.][!p][!y]` must necessarily return `False` whenever the third-from-last character is `.`. – Kyle Strand Dec 01 '14 at 22:08

3 Answers3

5

I personally prefer regex, but if you'd prefer fnmatch, you could do:

if fnmatch.fnmatch(file, '*[!p][!y]'):
        print(file)
user590028
  • 11,364
  • 3
  • 40
  • 57
  • 1
    Yes, I know I could do something like this. But I was actually looking for a *pattern* because I have to use it in a `--exclude` option of a program that uses fnmatch. – rubik Aug 15 '14 at 08:19
  • @rubik I don't get what the difficulty is in using this approach? Could you possibly explain a bit further? – Jon Clements Aug 15 '14 at 08:23
  • @JonClements: This approach is not difficult *per se*, it just doesn't fit my use-case. I'm using a program that analyzes some files. I can exclude the files with an `--exclude` option that takes fnmatch patterns. There is no `--include` option. Since I want to exclude everything that isn't a Python file, I was curious if there was such a pattern. – rubik Aug 15 '14 at 08:25
  • @JonClements: It is not. By pattern I mean the second argument to `fnmatch.fnmatch`. The pattern I'm looking for doesn't match Python files but matches everything else. Think of this situation as if I could only change the second argument of `fnmatch` but I couldn't touch anything else. – rubik Aug 15 '14 at 08:27
  • I've updated the pattern -- but I recommend you consider rethinking your pattern. A quick scan of fnmatch and glob's source shows they each are using regex under the hood. – user590028 Aug 15 '14 at 08:34
  • I see, but if I try it I always get `False`. I'm beginning to think that what I want to achieve is not possible with `fnmatch`. – rubik Aug 15 '14 at 08:35
  • Works for me... `[f for f in os.listdir('.') if fnmatch.fnmatch(f, '*[!py]')] == ['admin.pyc', 'ajax.pyc', ...]` – thebjorn Aug 15 '14 at 08:39
  • I tested it before posting -- are you sure you are getting false? Can you post a bit more your question about how your using the code? – user590028 Aug 15 '14 at 08:40
  • I don't know what I was doing wrong in the terminal. Trying it on real paths showed that it works as intended. Thanks! – rubik Aug 15 '14 at 08:43
  • No problem. FYI -- if you want to consider regex on another project, you can read this excellant article (which posts a specific example of how to match negated file extensions) -- https://docs.python.org/2/howto/regex.html#lookahead-assertions – user590028 Aug 15 '14 at 08:46
  • Thanks for the resource! Also, I discovered that your pattern fails to match `filename.yp`. I discovered that instead `*[!p][!y]` does its job. If you want to add this to your answer to make it more complete it would be great. – rubik Aug 15 '14 at 08:49
  • 4
    `*[!p][!y]` matches anything that does **not** have `p` as its second-from-last character **and** does not have `y` as its last character. For instance, Perl scripts with the `.pl` extension will be excluded. – Kyle Strand Dec 01 '14 at 22:11
  • 1
    @KyleStrand so the accepted answer regex '*[!p][!y]' is wrong, because it also exclude sth like: `.pl`, `hy`. Is there a way to exclude whole sentence instead of singular characters? – murt Dec 19 '22 at 16:10
  • @murt As far as I know, not with `fnmatch`, because `fnmatch` is based on Unix filename globs, which are much less powerful than true regex. In true regex, this is trivial: `(\.py)!` excludes anything with the characters `.py`. – Kyle Strand Dec 20 '22 at 18:31
3

I believe that the only absolutely correct answer is to generate the list of unwanted files, then find the difference between this list and the complete (unfiltered) file list. (This technique can be seen here, using set: https://stackoverflow.com/a/8645326/1858225)

There does not appear to be any built-in option for negating the search pattern in the fnmatch module.

For an example of why character-wise negation won't work, see my comment on user590028's answer.

Community
  • 1
  • 1
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
0

If files is a list of files, then this code will exclude all files matching *.py pattern using fnmatch.filter. This should be the correct answer.

exclude = '*.py'
for f in fnmatch.filter(files, exclude):
    files.remove(f)
panofish
  • 7,578
  • 13
  • 55
  • 96