python-mini-projects Diff_Util:用 Python 复刻 UNIX diff 的极简文件差异比对工具
2026/9/20 4:30:00 网站建设 项目流程

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.txt

README 将运行方式按操作系统区分为两类。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 给出的示例文件为v1v2

v1(3 行):

Bruce Alfred Jason

v2(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

输出格式约定如下,理解这三条规则即可读懂任意输入下的结果:

  1. 每一处差异占一对(或单独一行)[-] Line N: ...以红色标注第一个文件(original)中第 N 行的内容,[+] Line N: ...以绿色标注第二个文件(changed)中同一行号的内容;内容相同的行(如本例第 2 行Alfred)被静默跳过,不输出。
  2. 尾部增补行:当两文件行数不同时,多出来的行会按“较大文件”的符号身份追加报告。本例中v2多出的第 4、5 行(JokerRa's Al Ghul)以[+]绿色标出。
  3. 符号方向随参数顺序翻转:README 附带的截图(见文章开头配图右侧终端)还演示了反方向运行./diff.py v2 v1的结果——此时Batman/Joker/Ra's Al Ghul等变为红色[-]Bruce/Jason变为绿色[+]。也就是说,[-]/[+]的角色始终跟随命令行参数的顺序,而不是文件内容的“新旧”关系。

源码剖析:四个步骤完成一次 diff

diff.py 全文仅 48 行,执行流程可以拆成四步。

第一步:命令行校验与文件读入

在 Usage 校验之后,diff.py 将两个参数分别绑定到orignalchanged,并用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

从源码结构看,其逻辑是:行数较少的文件被视作“改动(删减)后的较小版本”,行数较多的文件被视作“包含新增内容的较大版本”symbolcolor变量因此被初始化为较大文件一侧的身份(绿色[+]或红色[-]),供第四步报告尾部增补行时复用。这也解释了上一节第 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 行(JasonRed Hood不同)时触发该分支,随后依次打印[+] Line 4: Joker[+] Line 5: Ra's Al Ghul,与 README 的示例输出完全对应。

行为边界:它能做什么、不能做什么

在引用或依赖该工具前,建议明确它的适用前提。以下两点均可从源码直接确认:

  1. 它是“行号对齐”的比对,而非真正的编辑距离 diff。循环按行号一一对应(line索引直接相等),并不计算最长公共子序列,因此不会生成 UNIXdiff那样的“hunk”块或最小编辑脚本。README 自称 minimal clone,正体现了这一取舍:它擅长回答“同一行号上内容改了什么、哪一方多了哪些行”,但面对中间插入/删除导致的整体错位时,会把错位之后的每一行都报告为差异。
  2. 尾部增补报告与末行是否相等耦合。从源码结构看,增补行输出语句位于“末行内容不同”的else分支内部(diff.py)。可以推断:若较短文件的最后一行恰好与较长文件对应行相同,该分支走的是continue,则较长文件多出的行将不会被报告。这是复刻版与成熟diff实现之间的一处行为差异,在评估输出完整性时需要留意。

此外,源码与 README 中orignal这一拼写(原文如此)贯穿变量名、Usage 提示与参数示例,阅读源码或排查日志时请按此拼写检索。

快速复现指南

按以下步骤可在本地完整复现 README 的示例输出:

  1. 进入 projects/Diff_Util 目录,执行pip install -r projects/Diff_Util/requirements.txt安装rich==10.11.0
  2. 按上文示例内容创建v1(3 行)与v2(5 行)两个纯文本文件;
  3. 依次运行python diff.py v1 v2python diff.py v2 v1(Linux/macOS 亦可用./diff.py v1 v2),对照本文“官方示例”小节的三组输出验证:差异行成对出现、相同行静默跳过、尾部增补行按参数顺序着色;
  4. 如需核对某段输出的生成逻辑,可按“四步流程”小节中的行号锚点直接定位 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询