文件名匹配 - 字符串中间部分
我有一个文件夹,里面的文件名格式是:LnLnnnnLnnn.txt
其中,L代表字母,n代表数字。例如:p2c0789c001.txt
我想根据第二个数字(也就是0789)是奇数还是偶数来把这些文件分开。
我目前只成功处理了第二个数字在0001到0009之间的情况,使用的代码是:
odd_files = []
for root, dirs, filenames in os.walk('.'):
for filename in fnmatch.filter(filenames, 'p2c000[13579]*.txt'):
odd_files.append(os.path.join(root, filename))
这样会返回这些文件:['./p2c0001c054.txt', './p2c0003c055.txt', './p2c0005c056.txt', './p2c0007c057.txt', './p2c0009c058.txt']
有没有什么建议可以让我处理任何四位数字的情况呢?
4 个回答
构建这种过滤器其实并没有什么特别复杂的地方。主要就是要仔细写出合适的正则表达式,然后用它来测试。当使用复杂的模式,特别是那些有很多重复部分的模式时,容易出现错误。因此,我喜欢定义一些辅助函数,这样可以让规则看起来更容易理解,也方便以后修改。
import re
import os
# helper functions for legible re construction
LETTER = lambda n='': '({0}{1})'.format('[A-Za-z]', n)
NUM = lambda n='': '({0}{1})'.format('\d', n)
FILENAME = LETTER() + NUM() + LETTER() + NUM('{4}') + LETTER() + NUM('{3}') + '\.txt'
FILENAME_RE = re.compile(FILENAME)
is_odd = lambda n: int(n) % 2 > 0
def odd_nnnn(f):
"""
Determine if the given filename `f` matches our desired LnLnnnnLnnn.txt pattern
with the second group of numbers (nnnn) odd.
"""
m = FILENAME_RE.search(f)
return m is not None and is_odd(m.group(4))
if __name__ == '__main__':
print "Search pattern:", FILENAME
files = ['./p2c0001c054.txt', './p2c0001c055.txt', './p2c0003c055.txt', './p2c0005c056.txt', './p2c0022c056.txt', './p2c0004c056.txt', './p2c0007c057.txt', './p2c0009c058.txt', './p2c8888c056.txt', ]
files = [ os.path.normpath(f) for f in files ]
root = '/users/test/whatever'
odd_paths = [ os.path.join(root, f) for f in files if odd_nnnn(f) ]
print odd_paths
唯一的缺点就是这样写会稍微多一些文字,特别是和Brad Beattie那种简洁的答案相比。
[更新] 后来我想到,定义正则表达式还有一种更简洁的方法:
FILENAME = "LnL(nnnn)Lnnn\.txt"
FILENAME_PAT = FILENAME.replace('L', r'[A-Za-z]').replace('n', r'\d')
FILENAME_RE = re.compile(FILENAME_PAT)
这个方法更贴近最初的 'LnLnnnLnnn.txt' 描述。匹配表达式需要从 m.group(4)
改为 m.group(1)
,因为这样只捕获了一个组。
如果事情变得有点复杂,你可以把它变成一个生成器,然后手动编写测试代码:
def odd_files_generator():
for root, dirs, filenames in os.walk('.'):
for filename in filenames:
if filename[6] in '13579':
yield filename
odd_files = list(odd_files_generator)
如果你的测试变得很难简洁地表达,可以把 if filename ...
这一行换成你明确的测试代码。
这样做可以吗?
import re
regex = re.compile("[a-z][0-9][a-z]([0-9]{4})[a-z][0-9]{3}.txt")
filter(lambda x: int(regex.match(x).groups()[0]) % 2 == 1, fnmatch)
最简单的解决办法就是把你的通配符扩展一下,让它能匹配更多的东西。
为此,我可能会这样做:
for filename in fnmatch.filter(filenames, '??????[13579]*.txt'):
这样做的话,它会匹配你值前面的任何字符,然后会匹配你通配符类中的任何奇怪值,最后会接受后面的任何东西。
这样有点麻烦,因为像aaaaaaa3alkjfdhalkjfshglkjzsdhfgs.txt这样的文件名也会被匹配到,这样就很糟糕。如果你知道你要处理的目录里的数据是比较规范的,那可能还可以接受。更好的办法是稍微具体一点。你可以用下面的表达式来实现:
'[a-z][0-0][a-z][0-9][0-9][0-9][13579][a-z][0-9][0-9][0-9].txt'
fnmatch.filter方法使用的是Unix风格的通配符。这意味着你可以使用以下符号:
? - 匹配任何单个字符
* - 匹配从零到所有的任何东西
[] - 这匹配一类东西,使用 - 来表示范围,使用 ! 来表示排除