python-mini-projects Diff_Util:用 Python 复刻 UNIX diff 的极简文件差异比对工具
【免费下载链接】python-mini-projectsA collection of simple python mini projects to enhance your python skills项目地址: https://gitcode.com/gh_mirrors/py/python-mini-projects
Diff_Util 是 python-mini-projects 仓库中一个对标 UNIXdiff命令行工具的迷你项目:它接收两个文件名作为命令行参数,按行比对内容差异,并用红/绿双色标注“被删除的行”与“新增的行”。读完本篇,你将掌握它的完整安装与运行方式、一条命令看懂差异输出的格式约定,并能从 diff.py 的 48 行源码中理解“变更方向推断”“逐行比较”和“尾部增补行报告”三个核心实现细节及其适用边界。
项目定位与运行前提
README 对项目的定义很直接:这是一个 UNIXdiff程序的极简克隆(minimal clone)。diff.py接收两个文件名作为命令行参数,比较它们之间的差异。
依赖只有一个,即用于终端彩色富文本输出的rich,版本锁定在 requirements.txt 中:
rich==10.11.0在运行前安装依赖(以 diff.py 所在目录为工作目录):
pip install -r projects/Diff_Util/requirements.txtREADME 将运行方式按操作系统区分为两类。Windows 下:
python diff.py <orignal_file> <changed_file>Linux / macOS 下(源码首行带有#!/usr/bin/env python3shebang,且文件带有可执行权限,因此支持直接执行;注意 README 中参数名orignal_file的拼写与源码变量名一致,此处为原文保留):
./diff.py <orignal_file> <changed_file>两个位置参数中,第一个被视为“原始文件”(original),第二个被视为“变更后的文件”(changed)。当参数不足两个时,diff.py 会打印 Usage 提示并以退出码 1 终止:
if len(sys.argv) < 3: print("Usage:") print("\tMust provide two file names as command-line arguments.") print("\tdiff.py <orignal_file> <changed_file>") exit(1)官方示例:两次运行的输出解读
README 给出的示例文件为v1与v2:
v1(3 行):
Bruce Alfred Jasonv2(5 行):
Batman Alfred Red Hood Joker Ra's Al Ghul运行./diff.py v1 v2后,输出为:
[-] Line 1: Bruce [+] Line 1: Batman [-] Line 3: Jason [+] Line 3: Red Hood [+] Line 4: Joker [+] Line 5: Ra's Al Ghul输出格式约定如下,理解这三条规则即可读懂任意输入下的结果:
- 每一处差异占一对(或单独一行):
[-] Line N: ...以红色标注第一个文件(original)中第 N 行的内容,[+] Line N: ...以绿色标注第二个文件(changed)中同一行号的内容;内容相同的行(如本例第 2 行Alfred)被静默跳过,不输出。 - 尾部增补行:当两文件行数不同时,多出来的行会按“较大文件”的符号身份追加报告。本例中
v2多出的第 4、5 行(Joker、Ra's Al Ghul)以[+]绿色标出。 - 符号方向随参数顺序翻转:README 附带的截图(见文章开头配图右侧终端)还演示了反方向运行
./diff.py v2 v1的结果——此时Batman/Joker/Ra's Al Ghul等变为红色[-],Bruce/Jason变为绿色[+]。也就是说,[-]/[+]的角色始终跟随命令行参数的顺序,而不是文件内容的“新旧”关系。
源码剖析:四个步骤完成一次 diff
diff.py 全文仅 48 行,执行流程可以拆成四步。
第一步:命令行校验与文件读入
在 Usage 校验之后,diff.py 将两个参数分别绑定到orignal与changed,并用open(..., "r").readlines()把两个文件按行读入列表。readlines()会保留每行的换行符,这正是后续输出中“原行自带换行、新行紧跟其后”排版效果的来源——源码在打印 original 行时显式使用end=""(diff.py),让行尾原有的\n自然结束该输出行。
第二步:用行数推断“变更方向”
这是本项目最有意思的设计。diff.py 并不预设哪个文件更新,而是在 diff.py 中比较两个列表的长度:
color = "green" symbol = f"[bold {color}][+]" # Determine which file has changed much. if len(changed_contents) <= len(orignal_contents): color = "red" symbol = f"[bold {color}][-]" smallest_sloc, largest_sloc = changed_contents, orignal_contents else: smallest_sloc, largest_sloc = orignal_contents, changed_contents从源码结构看,其逻辑是:行数较少的文件被视作“改动(删减)后的较小版本”,行数较多的文件被视作“包含新增内容的较大版本”。symbol与color变量因此被初始化为较大文件一侧的身份(绿色[+]或红色[-]),供第四步报告尾部增补行时复用。这也解释了上一节第 3 条格式规则:无论你把哪个文件放在第一个参数位置,行数更多的一方始终被标注为“增补”,行数更少的一方被标注为“删减”。
第三步:逐行比较并着色输出
核心比较循环在 diff.py:
for line in range(0, len(smallest_sloc)): if orignal_contents[line] == changed_contents[line]: # Ignore if the lines are same. continue else: # Display the changes on the respective lines of the files. print(f"[bold red][-] Line {line + 1}:[/bold red] {orignal_contents[line]}", end = "") print(f"[bold green][+] Line {line + 1}:[/bold green] {changed_contents[line]}")注意两点实现细节:
- 循环边界是
len(smallest_sloc)而非两个文件行数的最大值——比较只覆盖到较短文件用尽为止,较长文件多出的行留给下一步处理。 - 着色由
rich.print的标记语法完成(from rich import print覆盖了内置print),[bold red]/[bold green]分别渲染加粗红/绿色;行号统一采用从 1 计数的line + 1,与 UNIXdiff的行号习惯一致。
第四步:报告较大文件的尾部增补行
diff.py 把“尾部增补”的报告挂载在循环的最末一次比较上:
# Show the additions [+] or deletions [-] for the file that is the largest. if line == len(smallest_sloc) - 1: for new_line in range(line + 1, len(largest_sloc)): print(f"{symbol} Line {new_line + 1}:[/bold {color}] {largest_sloc[new_line]}")当比较到较短文件的最后一行(line == len(smallest_sloc) - 1)时,内层循环从line + 1遍历到较长文件末尾,用第二步确定的symbol/color把每一行多出来的内容输出。以./diff.py v1 v2为例:循环比较到第 3 行(Jason与Red Hood不同)时触发该分支,随后依次打印[+] Line 4: Joker与[+] Line 5: Ra's Al Ghul,与 README 的示例输出完全对应。
行为边界:它能做什么、不能做什么
在引用或依赖该工具前,建议明确它的适用前提。以下两点均可从源码直接确认:
- 它是“行号对齐”的比对,而非真正的编辑距离 diff。循环按行号一一对应(
line索引直接相等),并不计算最长公共子序列,因此不会生成 UNIXdiff那样的“hunk”块或最小编辑脚本。README 自称 minimal clone,正体现了这一取舍:它擅长回答“同一行号上内容改了什么、哪一方多了哪些行”,但面对中间插入/删除导致的整体错位时,会把错位之后的每一行都报告为差异。 - 尾部增补报告与末行是否相等耦合。从源码结构看,增补行输出语句位于“末行内容不同”的
else分支内部(diff.py)。可以推断:若较短文件的最后一行恰好与较长文件对应行相同,该分支走的是continue,则较长文件多出的行将不会被报告。这是复刻版与成熟diff实现之间的一处行为差异,在评估输出完整性时需要留意。
此外,源码与 README 中orignal这一拼写(原文如此)贯穿变量名、Usage 提示与参数示例,阅读源码或排查日志时请按此拼写检索。
快速复现指南
按以下步骤可在本地完整复现 README 的示例输出:
- 进入 projects/Diff_Util 目录,执行
pip install -r projects/Diff_Util/requirements.txt安装rich==10.11.0; - 按上文示例内容创建
v1(3 行)与v2(5 行)两个纯文本文件; - 依次运行
python diff.py v1 v2与python diff.py v2 v1(Linux/macOS 亦可用./diff.py v1 v2),对照本文“官方示例”小节的三组输出验证:差异行成对出现、相同行静默跳过、尾部增补行按参数顺序着色; - 如需核对某段输出的生成逻辑,可按“四步流程”小节中的行号锚点直接定位 diff.py 对应代码段。
Diff_Util 用最少的代码展示了终端 diff 工具的完整骨架:参数校验、按行数推断变更方向、逐行比较、富文本着色与尾部增补报告。对希望学习命令行参数处理、rich彩色输出以及“先定策略再写循环”这类结构思路的读者,它是一份小而完整的阅读材料。
【免费下载链接】python-mini-projectsA collection of simple python mini projects to enhance your python skills项目地址: https://gitcode.com/gh_mirrors/py/python-mini-projects
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考