import os import hashlib import time from datetime import datetime import argparse
class FileComparator: """文件比较工具,用于比较文件内容和属性"""
def __init__(self, buffer_size=65536):
"""初始化比较器
Args:
buffer_size: 读取文件时的缓冲区大小
"""
self.buffer_size = buffer_size
def get_file_hash(self, file_path, algorithm='sha256'):
"""计算文件的哈希值
Args:
file_path: 文件路径
algorithm: 哈希算法,默认为sha256
Returns:
文件的哈希值
"""
if not os.path.isfile(file_path):
raise ValueError(f"不是有效的文件: {file_path}")
hash_func = getattr(hashlib, algorithm, None)
if not hash_func:
raise ValueError(f"不支持的哈希算法: {algorithm}")
hash_obj = hash_func()
try:
with open(file_path, 'rb') as f:
while True:
data = f.read(self.buffer_size)
if not data:
break
hash_obj.update(data)
return hash_obj.hexdigest()
except Exception as e:
raise Exception(f"计算哈希值时出错: {str(e)}")
def compare_file_content(self, file1, file2, algorithm='sha256'):
"""比较两个文件的内容是否相同
Args:
file1: 第一个文件路径
file2: 第二个文件路径
algorithm: 哈希算法
Returns:
布尔值,表示两个文件内容是否相同
"""
hash1 = self.get_file_hash(file1, algorithm)
hash2 = self.get_file_hash(file2, algorithm)
return hash1 == hash2
def get_file_info(self, file_path):
"""获取文件的基本信息
Args:
file_path: 文件路径
Returns:
包含文件信息的字典
"""
if not os.path.exists(file_path):
return None
info = {
'path': file_path,
'size': os.path.getsize(file_path),
'modified_time': os.path.getmtime(file_path),
'modified_time_str': datetime.fromtimestamp(os.path.getmtime(file_path)).strftime('%Y-%m-%d %H:%M:%S'),
'created_time': os.path.getctime(file_path) if hasattr(os, 'getctime') else None,
'created_time_str': datetime.fromtimestamp(os.path.getctime(file_path)).strftime('%Y-%m-%d %H:%M:%S') if hasattr(os, 'getctime') else None,
'is_file': os.path.isfile(file_path),
'is_dir': os.path.isdir(file_path)
}
return info
def compare_file_properties(self, file1, file2, properties=None):
"""比较两个文件的属性
Args:
file1: 第一个文件路径
file2: 第二个文件路径
properties: 要比较的属性列表,默认为['size', 'modified_time']
Returns:
包含比较结果的字典
"""
if properties is None:
properties = ['size', 'modified_time']
info1 = self.get_file_info(file1)
info2 = self.get_file_info(file2)
if not info1 or not info2:
raise ValueError("无法获取文件信息")
result = {
'same': True,
'differences': {}
}
for prop in properties:
if prop not in info1 or prop not in info2:
continue
if info1[prop] != info2[prop]:
result['same'] = False
result['differences'][prop] = {
file1: info1[prop],
file2: info2[prop]
}
return result
class DirectoryComparator: """目录比较工具,用于比较两个目录的结构和内容"""
def __init__(self, file_comparator=None):
"""初始化目录比较器
Args:
file_comparator: 文件比较器实例,默认为None,将创建新实例
"""
self.file_comparator = file_comparator or FileComparator()
def compare_directories(self, dir1, dir2, compare_content=True, ignore_patterns=None):
"""递归比较两个目录
Args:
dir1: 第一个目录路径
dir2: 第二个目录路径
compare_content: 是否比较文件内容
ignore_patterns: 忽略的文件/目录模式列表
Returns:
包含比较结果的字典
"""
if not os.path.isdir(dir1) or not os.path.isdir(dir2):
raise ValueError("参数必须是有效的目录")
if ignore_patterns is None:
ignore_patterns = []
result = {
'only_in_dir1': [],
'only_in_dir2': [],
'different_files': [],
'same_files': [],
'different_dirs': []
}
# 获取两个目录中的所有条目
entries1 = set(os.listdir(dir1))
entries2 = set(os.listdir(dir2))
# 应用忽略模式
def should_ignore(entry):
for pattern in ignore_patterns:
if pattern in entry:
return True
return False
entries1 = {e for e in entries1 if not should_ignore(e)}
entries2 = {e for e in entries2 if not should_ignore(e)}
# 找出只在第一个目录中的条目
only_in_dir1 = entries1 - entries2
for entry in only_in_dir1:
full_path = os.path.join(dir1, entry)
result['only_in_dir1'].append(full_path)
# 找出只在第二个目录中的条目
only_in_dir2 = entries2 - entries1
for entry in only_in_dir2:
full_path = os.path.join(dir2, entry)
result['only_in_dir2'].append(full_path)
# 比较两个目录中都存在的条目
common_entries = entries1 & entries2
for entry in common_entries:
path1 = os.path.join(dir1, entry)
path2 = os.path.join(dir2, entry)
if os.path.isdir(path1) and os.path.isdir(path2):
# 递归比较子目录
sub_result = self.compare_directories(
path1, path2, compare_content, ignore_patterns
)
# 如果子目录有差异,记录下来
if (sub_result['only_in_dir1'] or sub_result['only_in_dir2'] or
sub_result['different_files'] or sub_result['different_dirs']):
result['different_dirs'].append(path1)
# 合并子目录的比较结果
for key in ['only_in_dir1', 'only_in_dir2', 'different_files', 'same_files', 'different_dirs']:
result[key].extend(sub_result[key])
elif os.path.isfile(path1) and os.path.isfile(path2):
# 比较文件
if compare_content:
are_same = self.file_comparator.compare_file_content(path1, path2)
else:
prop_result = self.file_comparator.compare_file_properties(path1, path2)
are_same = prop_result['same']
if are_same:
result['same_files'].append(path1)
else:
result['different_files'].append(path1)
else:
# 一个是文件,一个是目录
result['different_files'].append(path1)
return result
def generate_report(self, result, output_file=None):
"""生成比较报告
Args:
result: 比较结果
output_file: 输出文件路径,默认为None,将打印到控制台
Returns:
报告内容
"""
report = []
report.append("=" * 50)
report.append("目录比较报告")
report.append("生成时间: " + datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
report.append("=" * 50)
report.append("\n只在第一个目录中的条目:")
if result['only_in_dir1']:
for item in result['only_in_dir1']:
report.append(f"- {item}")
else:
report.append("- 无")
report.append("\n只在第二个目录中的条目:")
if result['only_in_dir2']:
for item in result['only_in_dir2']:
report.append(f"- {item}")
else:
report.append("- 无")
report.append("\n内容不同的文件:")
if result['different_files']:
for item in result['different_files']:
report.append(f"- {item}")
else:
report.append("- 无")
report.append("\n内容相同的文件:")
if result['same_files']:
report.append(f"- 共 {len(result['same_files'])} 个文件")
else:
report.append("- 无")
report.append("\n结构不同的子目录:")
if result['different_dirs']:
for item in result['different_dirs']:
report.append(f"- {item}")
else:
report.append("- 无")
report.append("\n" + "=" * 50)
report.append("比较总结")
report.append(f"只在第一个目录中的条目: {len(result['only_in_dir1'])}")
report.append(f"只在第二个目录中的条目: {len(result['only_in_dir2'])}")
report.append(f"内容不同的文件: {len(result['different_files'])}")
report.append(f"内容相同的文件: {len(result['same_files'])}")
report.append(f"结构不同的子目录: {len(result['different_dirs'])}")
report.append("=" * 50)
report_content = "\n".join(report)
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(report_content)
print(f"报告已保存到: {output_file}")
else:
print(report_content)
return report_content
def main(): """主函数,处理命令行参数并执行比较""" parser = argparse.ArgumentParser(description='比较两个文件或目录') parser.add_argument('path1', help='第一个文件或目录路径') parser.add_argument('path2', help='第二个文件或目录路径') parser.add_argument('-c', '--compare-content', action='store_true', help='比较文件内容(默认为比较文件属性)') parser.add_argument('-o', '--output', help='输出报告文件路径') parser.add_argument('-i', '--ignore', nargs='+', help='忽略的文件/目录模式列表')
args = parser.parse_args()
try:
if os.path.isfile(args.path1) and os.path.isfile(args.path2):
# 文件比较
file_comp = FileComparator()
if args.compare_content:
are_same = file_comp.compare_file_content(args.path1, args.path2)
print(f"文件内容是否相同: {'是' if are_same else '否'}")
else:
result = file_comp.compare_file_properties(args.path1, args.path2)
print(f"文件属性是否相同: {'是' if result['same'] else '否'}")
if not result['same']:
print("不同的属性:")
for prop, values in result['differences'].items():
print(f"- {prop}:")
print(f" {args.path1}: {values[args.path1]}")
print(f" {args.path2}: {values[args.path2]}")
# 输出文件信息
info1 = file_comp.get_file_info(args.path1)
info2 = file_comp.get_file_info(args.path2)
print("\n文件1信息:")
for key, value in info1.items():
if key not in ['path', 'is_file', 'is_dir']:
print(f"- {key}: {value}")
print("\n文件2信息:")
for key, value in info2.items():
if key not in ['path', 'is_file', 'is_dir']:
print(f"- {key}: {value}")
elif os.path.isdir(args.path1) and os.path.isdir(args.path2):
# 目录比较
dir_comp = DirectoryComparator()
result = dir_comp.compare_directories(
args.path1, args.path2,
compare_content=args.compare_content,
ignore_patterns=args.ignore
)
dir_comp.generate_report(result, args.output)
else:
print("错误: 请提供两个文件或两个目录进行比较")
parser.print_help()
except Exception as e:
print(f"发生错误: {str(e)}")
if name == "main": main()