有没有简单的方法(或可能)将fnmatch.translate
的输出转换为匹配零个或多个目录的正则表达式?
>>> import fnmatch
>>> fnmatch.translate("**/*.py")
'.*.*\\/.*\\.py\\Z(?ms)'
我想要的是将___
放在
dirs = ['a.py', 'b/a.py', 'b/c/a.py', 'b/c/d/a.py']
r = fnmatch.translate("**/*.py").replace(".*.*", "___")
这样
[d for d in dirs if re.match(r, d)] == dirs
这种尝试在前两个中并不匹配:
fnmatch.translate("**/*.py").replace('.*.*', "(.*/.*)*")
这与第一个匹配:
fnmatch.translate("**/*.py").replace('.*.*', "(.*/?)*")
这使解释器挂起:
fnmatch.translate("**/*.py").replace('.*.*', "(.*|(.*/.*))*")
我很满意答案,解释为什么它也不可能......
更新:用空字符串替换**/
并不起作用:
dirs = ['a.py', 'b/a.py', 'b/c/a.py', 'b/c/d/a.py']
def translate(pat, repl=None):
r = fnmatch.translate(pat)
if repl:
r = r.replace(".*.*", repl)
r = re.compile(r)
return [d for d in dirs if r.match(d)]
>>> print translate("**/*.py".replace("**/", ""))
['a.py', 'b/a.py', 'b/c/a.py', 'b/c/d/a.py'] # correct
>>> print translate("b/**/*.py".replace("**/", ""))
['b/a.py', 'b/c/a.py', 'b/c/d/a.py'] # correct
>>> print translate("b/**/b.py".replace("**/", ""))
['b/a.py'] # incorrect
答案 0 :(得分:2)
实际上 - 您根本不需要更改.*.*
,只需更改一个.*
;相反,您需要将单一,未配对的 .*
更改为[^/]*
。
因此,所需的实际翻译表是 - 根本不使用fnmatch.translate()
,而是自己滚动:
`?` -> `.`
`.` -> `[.]`
`**/` -> `(?:.*/)?`
`*` -> `[^/]*`
正确处理[!foo]
及其同类最好从上游translate
代码中复制逻辑。
应用这些规则会将**/*.py
转换为(?:.*/)?[^/]*[.]py
,它会与您问题中的所有四个名称相匹配:
>>> import re
>>> dirs = ['a.py', 'b/a.py', 'b/c/a.py', 'b/c/d/a.py']
>>> py_re = re.compile('(?:.*/)?[^/]*[.]py')
>>> [ 'Matches' if py_re.match(x) else 'Non-Matching' for x in dirs ]
['Matches', 'Matches', 'Matches', 'Matches']
变换的实现:
def build_re(glob_str):
opts = re.compile('([.]|[*][*]/|[*]|[?])|(.)')
out = ''
for (pattern_match, literal_text) in opts.findall(glob_str):
if pattern_match == '.':
out += '[.]'
elif pattern_match == '**/':
out += '(?:.*/)?'
elif pattern_match == '*':
out += '[^/]*'
elif pattern_match == '?':
out += '.'
elif literal_text:
out += literal_text
return out
答案 1 :(得分:2)
这是Charles'的非正则表达式实现。翻译算法。
update1:此版本处理前导!
作为整个模式的否定,类似于node.js' minimatch(https://github.com/isaacs/minimatch#comparisons-to-other-fnmatchglob-implementations)。
def charles(pat):
r = ""
negate = int(pat.startswith('!'))
i = negate
while i < len(pat):
if pat[i:].startswith('**/'):
r += "(?:.*/)?"
i += 3
elif pat[i] == "*":
r += "[^/]*"
i += 1
elif pat[i] == ".":
r += "[.]"
i += 1
elif pat[i] == "?":
r += "."
i += 1
else:
r += pat[i]
i += 1
def match(d):
m = re.match(r, d)
return not m if negate else m
return [d for d in dirs if match(d)]
print charles("**/*.py")
print charles("b/**/*.py")
print charles("b/**/a.py")
print charles("!b/**/a.py")
打印
# (?:.*/)?[^/]*[.]py
['a.py', 'b/a.py', 'b/c/a.py', 'b/c/d/a.py']
# b/(?:.*/)?[^/]*[.]py
['b/a.py', 'b/c/a.py', 'b/c/d/a.py']
# b/(?:.*/)?a[.]py
['b/a.py', 'b/c/a.py', 'b/c/d/a.py']
# b/(?:.*/)?a[.]py
['a.py']
update2:处理否定fnmatch
的处理方式有点复杂(基本上fnmatch
对待[!...]
类似于正则表达式[^...]
) :
def translate(pat):
r = ""
i = 0
L = len(pat)
while i < L:
if pat[i:].startswith('**/'):
r += "(?:.*/)?"
i += 3
elif pat[i] == "*":
r += "[^/]*"
i += 1
elif pat[i] == ".":
r += "[.]"
i += 1
elif pat[i] == "?":
r += "."
i += 1
elif pat[i] == '[':
i += 1
j = i
if j < L and pat[j] == '!':
j += 1
if j < L and pat[j] == ']': # make sure [!] => \[\!\]
j += 1
while j < L and pat[j] != ']':
j += 1
if j >= L:
r += '\\[' # didn't find a closing ']', backtracking
else:
stuff = pat[i:j].replace('\\', '\\\\')
i = j+1
if stuff[0] == '!':
stuff = '^' + stuff[1:] # translate negation
elif stuff[0] == '^':
stuff = '\\' + stuff # quote ^ character
r = '%s[%s]' % (r, stuff)
else:
r += re.escape(pat[i])
#r += pat[i]
i += 1
r += '\\Z(?ms)'
return r
(否定代码几乎逐字逐句地从fnmatch中偷走了。)