python删除匹配未知模式的旧文件(棘手)

我的服务器正在装满,我需要自动删除文件。 文件通常会每天添加到我的服务器,但有时候会有暂停,使得它们每两周或每月一次。 他们停下来几个月,然后重新开始,这是不可预测的。

我的脚本需要删除超过30天的文件, 但始终保留它find的任何文件模式的最新5个文件 。 这是棘手的部分。

关于文件的唯一可预测的事情/模式是文件总是包含某个地方的yyyymmddhhmmss时间戳,以及某些重复的模式,文件名的其他部分并不总是可预测的。 如果一个文件没有时间戳,我不想删除它。

一个示例目录我有这样的东西

20121118011335_team1-pathway_Truck_Report_Data_10342532.zip
20121119011335_team1-pathway_Truck_Report_Data_102345234.zip
20121120011335_team1-pathway_Truck_Report_Data_10642224.zip
20121121011335_team1-pathway_Truck_Report_Data_133464.zip
20121122011335_team1-pathway_Truck_Report_Data_126434344.zip
20121123011335_team1-pathway_Truck_Report_Data_12444656.zip
20121124011335_team1-pathway_Truck_Report_Data_1624444.zip
20121125011335_team1-pathway_Truck_Report_Data_3464433.zip
randomefilewithnodate.zip
20121119011335_team2-Paper_Size_Report_336655.zip
20121120011335_team2-Paper_Size_Report_336677.zip
20121121011335_team2-Paper_Size_Report_338877.zip
20121122011335_team2-Paper_Size_Report_226688.zip
20121123011335_team2-Paper_Size_Report_776688.zip
20121124011335_team2-Paper_Size_Report_223355.zip
20121125011335_team2-Paper_Size_Report_111111.zip

在这种情况下,我的脚本应该只删除该第一个模式的最旧的3个文件20121118011335_team1-pathway_Truck_Report_Data_10342532.zip
20121119011335_team1-pathway_Truck_Report_Data_102345234.zip
20121120011335_team1-pathway_Truck_Report_Data_10642224.zip

和第二个模式中最早的两个文件
20121119011335_team2-Paper_Size_Report_336655.zip
20121120011335_team2-Paper_Size_Report_336677.zip

这样它保留了5个最新的文件,并没有date触摸文件

我的问题是我没有办法知道什么会遵循yyyymmddhhmmss_我只知道这将是各种迭代的yyyymmddhhmmss_something_consistent_random_random或yyyymmddhhmmss_something_consistent_something_consistent_random_random.xyz

到目前为止,我已经提出了正则expression式来匹配如果时间戳存在,但我想不出如何让我的脚本足够聪明,以检测文件的模式的其余部分,并保持5天的模式。

任何想法都欢迎! 下面的脚本不完美, 我可以修复这些小错误。

我真的需要帮助保持5个最新的文件部分主要

奖金问题是epoc时间部分。

def myCleansingMethod(self, client) # Get rid of things older than 30 days # 30 days has this many seconds 30 * 24 * 60 * 60 numberOfSeconds = 2592000 # establish what the epoc time of the oldest file I want to keep is oldestFileThatIWantToKeep = time.time() - numberOfSeconds #establish my working directory workingDirectory = "/home/files/%s" % (client) try: files = os.listdir(workingDirectory) except: print "Could not find directory" return files.sort() for file in files: # define Full File Name (path + file) fullFileName = "%s/%s" % (workingDirectory, file) # make sure the file contains yyyymmddhhmmss match = re.search(r'[0-9]{4}(1[0-2]|0[1-9])(3[01]|[12][0-9]|0[1-9])([01]\d|2[0123])([0-5]\d){2}', file) if match: #get what was matched in the RegEx fileTime = match.group() #convert fileTime to Epoc time fileTimeToEpoc = (fileTime + NOT SURE HOW TO DO THIS PART YET) if fileTimeToEpoc < oldestFileThatIWantToKeep AND (CODE THAT MAKES SURE THERE ARE AT LEAST 5 FILES OF THE SAME PATTERN PRESENT) : print "Delete file: %s\t%s" % (fileTimeToEpoc, fullFileName) command = "rm -Rf %s" % fullFileName print command os.system (command) else: pass else: pass 

这是一个很好的任务,我主要从itertools大量使用功能模式。 我喜欢使用迭代器,因为它们是可扩展的,即使是巨大的列表,所涉及的功能性思想也使得代码易读易维护。

首先,从itertools和datetime导入我们需要的东西:

 from itertools import groupby, chain from datetime import datetime 

获取您的示例文件名列表:

 filenames = """20121118011335_team1-pathway_Truck_Report_Data_10342532.zip 20121119011335_team1-pathway_Truck_Report_Data_102345234.zip 20121120011335_team1-pathway_Truck_Report_Data_10642224.zip 20121121011335_team1-pathway_Truck_Report_Data_133464.zip 20121122011335_team1-pathway_Truck_Report_Data_126434344.zip 20121123011335_team1-pathway_Truck_Report_Data_12444656.zip 20121124011335_team1-pathway_Truck_Report_Data_1624444.zip 20121125011335_team1-pathway_Truck_Report_Data_3464433.zip randomefilewithnodate.zip 20121119011335_team2-Paper_Size_Report_336655.zip 20121120011335_team2-Paper_Size_Report_336677.zip 20121121011335_team2-Paper_Size_Report_338877.zip 20121122011335_team2-Paper_Size_Report_226688.zip 20121123011335_team2-Paper_Size_Report_776688.zip 20121124011335_team2-Paper_Size_Report_223355.zip 20121125011335_team2-Paper_Size_Report_111111.zip""".split("\n") 

一些帮手功能。 名字应该是自我解释。

 def extract_date(s): return datetime.strptime(s.split("_")[0], "%Y%m%d%H%M%S") def starts_with_date(s): try: extract_date(s) return True except Exception: return False 

如果它不包含所有的情况下,你可能想要调整的下一个方法 – 对于你的样本数据,它可以。

 def get_name_root(s): return "".join(s.split(".")[0].split("_")[1:-1]) def find_files_to_delete_for_group(group): sorted_group = sorted(group, key=extract_date) return sorted_group[:-5] 

现在,整个例程可以通过迭代来完成。 首先,我筛选文件名列表,所有不以数据开头(以您的格式)的文件都被过滤掉。 然后,其余的按照他们的“名字根”分组(不能想象一个更好的名字)。

 fn_groups = groupby( filter( starts_with_date, filenames), get_name_root ) 

现在,对于每个组,我应用过滤方法(请参阅上文)来查找所有不包含五个最新日期的文件名。 发现每个组是chain编辑的,也就是说,一个迭代器是从多个列表创建的:

 fns_to_delete = chain(*[find_files_to_delete_for_group(g) for k, g in fn_groups]) 

最后,为了方便检查结果,我将迭代器转换为列表并打印出来:

 print list(fns_to_delete) 

这个脚本的输出是:

 ['20121118011335_team1-pathway_Truck_Report_Data_10342532.zip', '20121119011335_team1-pathway_Truck_Report_Data_102345234.zip', '20121120011335_team1-pathway_Truck_Report_Data_10642224.zip', '20121119011335_team2-Paper_Size_Report_336655.zip', '20121120011335_team2-Paper_Size_Report_336677.zip'] 

如果有什么不清楚的,就问。

下面是整个脚本,简单的介绍:

 from itertools import groupby, chain from datetime import datetime filenames = """20121118011335_team1-pathway_Truck_Report_Data_10342532.zip 20121119011335_team1-pathway_Truck_Report_Data_102345234.zip 20121120011335_team1-pathway_Truck_Report_Data_10642224.zip 20121121011335_team1-pathway_Truck_Report_Data_133464.zip 20121122011335_team1-pathway_Truck_Report_Data_126434344.zip 20121123011335_team1-pathway_Truck_Report_Data_12444656.zip 20121124011335_team1-pathway_Truck_Report_Data_1624444.zip 20121125011335_team1-pathway_Truck_Report_Data_3464433.zip randomefilewithnodate.zip 20121119011335_team2-Paper_Size_Report_336655.zip 20121120011335_team2-Paper_Size_Report_336677.zip 20121121011335_team2-Paper_Size_Report_338877.zip 20121122011335_team2-Paper_Size_Report_226688.zip 20121123011335_team2-Paper_Size_Report_776688.zip 20121124011335_team2-Paper_Size_Report_223355.zip 20121125011335_team2-Paper_Size_Report_111111.zip""".split("\n") def extract_date(s): return datetime.strptime(s.split("_")[0], "%Y%m%d%H%M%S") def starts_with_date(s): try: extract_date(s) return True except Exception: return False def get_name_root(s): return "".join(s.split(".")[0].split("_")[1:-1]) def find_files_to_delete_for_group(group): sorted_group = sorted(group, key=extract_date) return sorted_group[:-5] fn_groups = groupby( filter( starts_with_date, filenames), get_name_root ) fns_to_delete = chain(*[find_files_to_delete_for_group(g) for k, g in fn_groups]) print list(fns_to_delete) 

你需要做的不是一个编码问题,而是一个定义问题,所以不能通过编写更好的代码来解决:-)

为什么20121118011335_team1-pathway_Truck_Report_Data_10342532.zip20121119011335_team1-pathway_Truck_Report_Data_102345234.zip20121119011335_team1-pathway_Truck_Report_Data_102345234.zip是同一组的一部分? 您(作为人类)如何认识到重要的共同部分是_team1-pathway_Truck_Report_Data_而不是_team1-pathway_Truck_Report_Data_1

回答这个问题(我怀疑答案会涉及“下划线”和/或“数字”字样),你将有一个前进的方向。

我只知道这将是各种迭代的yyyymmddhhmmss_something_consistent_random_random或yyyymmddhhmmss_something_consistent_something_consistent_random_random.xyz

如果这是所有可能的变化,那么我会说你需要寻找下划线包围的共同初始序列。 这是有效的,因为随机的东西总是在最后,所以如果你想将文件扩展名包括在内,那么你必须特别对待它(例如把它移到你比较的字符串的前面)。 如果您发现有几个共有三个“单词”但不是四个的文件,那么您认为第四个块是“随机的”,三个块是“一致的”。 然后,按日期排序该类型的所有文件,从列表中取出最新的五个文件,并删除超过30天的剩余文件。

找到这些常见的初始序列的“显而易见”的方法是按日期以外的组件的字典顺序对文件名进行排序。 然后,具有共同初始序列的文件是相邻的,因此您可以遍历列表,将每个文件与当前最长的文件运行进行比较,并将其与通用前缀进行比较。

在编写代码时,请确保在以下情况下可以正确处理:

 <some_date>_truck1_548372.zip <some_date>_truck1_847284.zip <some_date>_truck1_data_4948739.zip <some_date>_truck1_data_9487203.zip 

也就是说,确保你知道在这种情况下(“truck1”)还是两个组(“truck1”和“truck1_data”)是否处理​​一个组。 这很重要,因为您可能需要从要求中排除任何truck1_data文件以保留5个truck1文件。


一种不同的方法:

  • 找到超过30天的所有文件(例如<some_date>_truck1_57349.zip )并将其从最旧到最新排序
  • 为每个文件寻找“权限”来删除它如下:
    • 从文件名开头删除日期
    • 搜索所有文件(不只是那些超过30天),忽略自己的日期,有一个共同的初始下划线包围这个文件的子字符串(所以这里我们找到了truck1文件和truck1_data文件)
    • 找到这些文件,找到至少两个共享的最长的子串( truck1_data
    • 如果目标文件不共享该子字符串,则从集合中删除所有具有公共子字符串的文件,并重复上一步骤(现在我们只truck1文件)
    • 目标文件共享子字符串后,对其进行计数。 如果至少有5个,则删除目标文件。

如上所述,这是不必要的缓慢,但我认为它简单地说明了这一点。 在最后一步中,实际上可以删除其余5个文件中的所有文件,并将其他5个文件从未来考虑中删除,因为您已经标识了一组文件。 同样,当你删除所有具有比它们与目标文件共享的子字符串长的文件时,你已经确定了一个组,并且可以把它作为一个整体处理,而不是把它扔回海中以备将来识别。

关于文件的唯一可预测的事情/模式是文件总是包含一个yyyymmddhhmmss时间戳和某些重复的模式

要允许yyyymmddhhmmss在文件名中的任何位置,并自动查找重复模式,可以先从文件名中删除yyyymmddhhmmss ,然后使用至少重复两次的最长前缀作为重复模式。

 import os from itertools import groupby from os.path import commonprefix def files_to_delete(topdir): for rootdir, dirs, files in os.walk(topdir): # find files with yyyymmddhhmmss files_with_date = [] for filename in files: for m in re.finditer(r"(?:^|\D)(\d{14})(?:\D|$)", filename): date = parse_date(m.group(1)) if date is not None: # found date in the filename # strip date no_date = filename[:m.start(1)] + filename[m.end(1):] # add to candidates for removal files_with_date.append((no_date, date, filename)) break # find repeating pattern files_with_date.sort() # sort by filename with a removed date # given ["team1-a", "team2-b", "team2-c"] # yield [["team1-a"], ["team2-b", "team2-c"]] where # roots are "team1" and "team2" # reject [["team1-a", "team2-b", "team2-c"]] grouping (root "team") # because the longer root "team2" occurs more than once roots = [commonprefix(a[0],b[0]) for a,b in pairwise(files_with_date)] roots.sort(key=len, reverse=True) # longest roots first def longest_root(item): no_date = item[0] return next(r for r in roots if no_date.startswith(r)) or no_date for common_root, group in groupby(files_with_date, key=longest_root): # strip 5 newest items (sort by date) for _, d, filename in sorted(group, key=lambda item: item[1])[:-5]: if d < month_ago: # older than 30 days yield os.path.join(rootdir, filename) 

注: ['team1-a', 'team2-b', 'team3-c', ...]组合在一起[['team1-a', 'team2-b', 'team3-c', ...]]使用'team'作为重复模式,即如果“重复模式”不在文件列表中重复,上述算法失败。

公用事业:

 from datetime import datetime, timedelta from itertools import izip, tee month_ago = datetime.utcnow() - timedelta(days=30) def parse_date(yyyymmddhhmmss): try: return datetime.strptime(yyyymmddhhmmss, "%Y%m%d%H%M%S") except ValueError: return None def pairwise(iterable): # itertools recipe a, b = tee(iterable) next(b, None) return izip(a, b) 

要删除一个文件,你可以调用os.remove(path)而不是os.system()

如果你可以在将来改变你的文件的命名方案,以确定性更强,例如在文件名模式中使用[] ,那么你可以提取root为:

 root = re.match(r'[^[]*\[([^]]+)\]', filename).group(1) 

我的建议:

  • 搜索文件,直到遇到一个名称包含14位数字的行。
  • 检查这些数字是否可以是时间戳。
  • 搜索名称包含相同余数的所有其他文件。
  • 按照日期的降序对它们进行排序。
  • 从有序文件列表中的第六个文件开始删除所有超过5天的文件。

如果.zip之前的随机部分总是由数字和下划线组成,那么可以使用这个正则表达式

 (\d{14})_(.*?)[_\d]+\.zip 

比赛中的第一组是一个可能的日期,你可以使用datetime.datetime.strptime来检查和解析。 第二组是你用于分组的不变部分。

此功能将您的文件名与日期戳转换为纪元时间它不使用正则表达式您必须在某处import time

 def timestamp2epoch(str): x=[] for v in (str[0:4],str[4:6],str[6:8],str[8:10],str[10:12],str[12:14],0,0,0): x.append(int(v)) return time.mktime(x) 

只要将问题分离出来,有一个方法可以找到模式,另一个找到日期和删除东西

 import os import subprocess def find_patterns(): filenames = os.walk("/path/to/my/data/dir").next()[2] parsed_patterns = map(lambda file: "_".join(file.split("_")[1:-1]),filenames) unique_patterns = list(set(parsed_patterns)) return unique_patterns def delete_dates_after_past_five_days(unique_patterns): filenames = os.walk("/path/to/my/data/dir").next()[2] for pattern in unique_patterns: matched_files = filter(lambda file: pattern in file, filenames) sorted_files = sorted(matched_files) #Will sort by date if len(sorted_files) > 5: for file in sorted_files[:-5]: subprocess.call(["rm", os.path.join("/path/to/my/data/dir", file)]) unique_patterns = find_patterns() delete_dates_from_past_five_days(unique_patterns)