Linux使用diff命令对比文件内容差异

 更新时间:2026年03月02日 08:42:41   作者:Jinkxs  
在日常开发、系统运维和版本控制中,我们经常需要比较两个文件之间的差异,Linux 系统自带的 diff 命令就是为此而生的强大工具,本文将带你深入探索 diff 命令的使用方法、输出格式、高级选项,并通过 Java 代码示例展示如何在程序中实现类似功能,需要的朋友可以参考下

在日常开发、系统运维和版本控制中,我们经常需要比较两个文件之间的差异。Linux 系统自带的 diff 命令就是为此而生的强大工具。它不仅可以快速定位文本文件的不同之处,还能输出结构化的差异报告,帮助开发者理解变更内容。本文将带你深入探索 diff 命令的使用方法、输出格式、高级选项,并通过 Java 代码示例展示如何在程序中实现类似功能,最后还会介绍一些实用技巧和最佳实践。

什么是diff命令?

diffdifference 的缩写,是 Unix/Linux 系统中最基础也是最重要的文本比较工具之一。它的主要作用是比较两个文件或目录的内容,找出它们之间的不同点,并以人类可读或机器可解析的方式输出结果。

最基本的用法非常简单:

diff file1.txt file2.txt

如果两个文件完全相同,则不会有任何输出;如果有差异,diff 会告诉你哪些行被修改、添加或删除。

基础语法与常用选项

基本语法结构

diff [选项] 文件1 文件2

常用选项一览

选项描述
-u--unified使用统一格式输出(最常用)
-c--context使用上下文格式输出
-y--side-by-side并排显示差异
-r--recursive递归比较子目录中的文件
-q--brief仅报告文件是否不同
-w--ignore-all-space忽略所有空白字符差异
-B--ignore-blank-lines忽略空行差异
-i--ignore-case忽略大小写差异

输出格式详解

默认格式(Normal diff)

这是最原始的格式,适合机器处理,但对人不太友好:

$ cat file1.txt
Hello World
This is line 2
Goodbye

$ cat file2.txt
Hello Linux
This is line 2
Farewell

$ diff file1.txt file2.txt
1c1
< Hello World
---
> Hello Linux
3c3
< Goodbye
---
> Farewell

解释:

  • 1c1 表示第1行被“更改”(change),对应目标文件的第1行。
  • < 表示来自第一个文件的内容。
  • > 表示来自第二个文件的内容。
  • --- 分隔符。

统一格式(Unified diff,推荐)

使用 -u 选项:

$ diff -u file1.txt file2.txt
--- file1.txt	2024-06-01 10:00:00.000000000 +0800
+++ file2.txt	2024-06-01 10:05:00.000000000 +0800
@@ -1,3 +1,3 @@
-Hello World
+Hello Linux
 This is line 2
-Goodbye
+Farewell

解读:

  • ---+++ 分别表示旧文件和新文件。
  • @@ -1,3 +1,3 @@ 是“块头”,表示从第1行开始,共3行。
  • - 开头的行表示被删除的内容。
  • + 开头的行表示新增的内容。
  • 无符号的行是上下文,未变化。

这种格式是 Git、SVN 等版本控制系统默认使用的格式,非常适合补丁生成和代码审查。

上下文格式(Context diff)

使用 -c 选项:

$ diff -c file1.txt file2.txt
*** file1.txt	2024-06-01 10:00:00.000000000 +0800
--- file2.txt	2024-06-01 10:05:00.000000000 +0800
***************
*** 1,3 ****
! Hello World
  This is line 2
! Goodbye
--- 1,3 ----
! Hello Linux
  This is line 2
! Farewell
  • *** 表示原文件。
  • --- 表示目标文件。
  • ! 表示该行有变化。
  • 空格开头表示未变化的上下文。

并排格式(Side-by-side)

使用 -y 选项:

$ diff -y file1.txt file2.txt
Hello World                           | Hello Linux
This is line 2                          This is line 2
Goodbye                               | Farewell
  • | 表示该行有差异。
  • < 表示左文件独有。
  • > 表示右文件独有。

你可以结合 -W 设置宽度:

diff -y -W 80 file1.txt file2.txt

高级用法与实战技巧

忽略空白差异

有时候我们只关心实质内容的变化,不希望因空格或换行符导致误报:

diff -w file1.txt file2.txt   # 忽略所有空白
diff -b file1.txt file2.txt   # 忽略空格数量差异
diff -B file1.txt file2.txt   # 忽略空行

忽略大小写

diff -i file1.txt file2.txt

仅判断是否不同

如果你只需要知道两个文件是否一样,不需要具体内容:

diff -q file1.txt file2.txt
# 输出:Files file1.txt and file2.txt differ

递归比较目录

diff -r dir1/ dir2/

加上 -q 可以只列出不同的文件名:

diff -rq dir1/ dir2/

生成补丁文件(Patch)

统一格式特别适合生成补丁:

diff -u old_version.c new_version.c > my_patch.patch

应用补丁:

patch old_version.c < my_patch.patch

实际应用场景

场景一:配置文件变更追踪

系统管理员常需对比 /etc/ 下配置文件的历史版本:

diff -u /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf

场景二:代码版本比对

在没有 Git 的环境下,手动对比两个版本的源码:

diff -ur src_v1/ src_v2/

场景三:日志分析

对比两次运行的日志输出,快速定位异常:

diff -u run1.log run2.log | grep "^+"  # 查看新增的错误行

场景四:自动化测试断言

在 CI/CD 流程中,用 diff 检查实际输出是否符合预期:

if diff expected_output.txt actual_output.txt > /dev/null; then
    echo "✅ 测试通过"
else
    echo "❌ 测试失败"
    exit 1
fi

在 Java 中模拟diff功能

虽然 Java 标准库没有内置的 diff 工具,但我们可以通过第三方库或自己实现算法来达成类似效果。

方法一:使用 Apache Commons Text(推荐)

Apache Commons Text 提供了 DiffBuilder 类,可以生成简易差异报告。

首先添加 Maven 依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.10.0</version>
</dependency>

Java 示例代码:

import org.apache.commons.text.diff.StringsComparator;
import org.apache.commons.text.diff.EditScript;
import org.apache.commons.text.diff.EditCommand;
import org.apache.commons.text.diff.DeleteCommand;
import org.apache.commons.text.diff.InsertCommand;
import org.apache.commons.text.diff.KeepCommand;

import java.util.List;

public class SimpleDiffExample {

    public static void main(String[] args) {
        String original = "Hello World\nThis is line 2\nGoodbye";
        String revised = "Hello Linux\nThis is line 2\nFarewell";

        StringsComparator comparator = new StringsComparator(original, revised);
        EditScript<Character> script = comparator.getScript();

        System.out.println("=== Java 模拟 diff 输出 ===");
        int origLine = 1, revLine = 1;
        StringBuilder currentOriginal = new StringBuilder();
        StringBuilder currentRevised = new StringBuilder();

        for (EditCommand<Character> cmd : script) {
            if (cmd instanceof KeepCommand) {
                char ch = ((KeepCommand<Character>) cmd).getCharacter();
                if (ch == '\n') {
                    System.out.printf(" %s", currentOriginal.toString());
                    currentOriginal.setLength(0);
                    currentRevised.setLength(0);
                    origLine++;
                    revLine++;
                } else {
                    currentOriginal.append(ch);
                    currentRevised.append(ch);
                }
            } else if (cmd instanceof DeleteCommand) {
                char ch = ((DeleteCommand<Character>) cmd).getCharacter();
                if (ch == '\n') {
                    System.out.printf("-%s\n", currentOriginal.toString());
                    currentOriginal.setLength(0);
                    origLine++;
                } else {
                    currentOriginal.append(ch);
                }
            } else if (cmd instanceof InsertCommand) {
                char ch = ((InsertCommand<Character>) cmd).getCharacter();
                if (ch == '\n') {
                    System.out.printf("+%s\n", currentRevised.toString());
                    currentRevised.setLength(0);
                    revLine++;
                } else {
                    currentRevised.append(ch);
                }
            }
        }

        // 处理最后一行(如果没有换行符结尾)
        if (currentOriginal.length() > 0) {
            System.out.printf("-%s\n", currentOriginal);
        }
        if (currentRevised.length() > 0) {
            System.out.printf("+%s\n", currentRevised);
        }
    }
}

输出示例:

=== Java 模拟 diff 输出 ===
-Hello World
+Hello Linux
 This is line 2
-Goodbye
+Farewell

这已经非常接近 diff -u 的简化版了!

方法二:使用 java-diff-utils 库

这是一个更专业的 Java Diff 库,支持多种输出格式。

Maven 依赖:

<dependency>
    <groupId>io.github.java-diff-utils</groupId>
    <artifactId>java-diff-utils</artifactId>
    <version>4.12</version>
</dependency>

Java 示例:

import difflib.DiffUtils;
import difflib.Patch;
import difflib.Delta;
import difflib.ChangeDelta;
import difflib.DeleteDelta;
import difflib.InsertDelta;

import java.util.Arrays;
import java.util.List;

public class JavaDiffUtilsExample {

    public static void main(String[] args) {
        List<String> original = Arrays.asList(
            "Hello World",
            "This is line 2",
            "Goodbye"
        );

        List<String> revised = Arrays.asList(
            "Hello Linux",
            "This is line 2",
            "Farewell"
        );

        Patch<String> patch = DiffUtils.diff(original, revised);

        System.out.println("=== 使用 java-diff-utils 的差异报告 ===");
        for (Delta<String> delta : patch.getDeltas()) {
            System.out.println(delta);
        }
    }
}

输出:

=== 使用 java-diff-utils 的差异报告 ===
Delta{original=Chunk[0:1]=["Hello World"], revised=Chunk[0:1]=["Hello Linux"]}
Delta{original=Chunk[2:3]=["Goodbye"], revised=Chunk[2:3]=["Farewell"]}

你还可以自定义格式化器,生成类似 diff -u 的输出:

import difflib.DiffUtils;
import difflib.Patch;
import difflib.Delta;
import difflib.Chunk;

public class UnifiedDiffExample {

    public static void printUnifiedDiff(List<String> original, List<String> revised, String originalName, String revisedName) {
        Patch<String> patch = DiffUtils.diff(original, revised);

        System.out.printf("--- %s\n", originalName);
        System.out.printf("+++ %s\n", revisedName);

        for (Delta<String> delta : patch.getDeltas()) {
            Chunk<String> orig = delta.getOriginal();
            Chunk<String> rev = delta.getRevised();

            int startOrig = orig.getPosition() + 1;
            int sizeOrig = orig.size();
            int startRev = rev.getPosition() + 1;
            int sizeRev = rev.size();

            System.out.printf("@@ -%d,%d +%d,%d @@\n", startOrig, sizeOrig, startRev, sizeRev);

            // 打印原始内容(带 -)
            for (String line : orig.getLines()) {
                System.out.println("-" + line);
            }

            // 打印修订内容(带 +)
            for (String line : rev.getLines()) {
                System.out.println("+" + line);
            }
        }
    }

    public static void main(String[] args) {
        List<String> original = Arrays.asList(
            "Hello World",
            "This is line 2",
            "Goodbye"
        );

        List<String> revised = Arrays.asList(
            "Hello Linux",
            "This is line 2",
            "Farewell"
        );

        printUnifiedDiff(original, revised, "file1.txt", "file2.txt");
    }
}

输出:

--- file1.txt
+++ file2.txt
@@ -1,1 +1,1 @@
-Hello World
+Hello Linux
@@ -3,1 +3,1 @@
-Goodbye
+Farewell

完美复刻 diff -u

diff 算法原理简析

diff 命令背后的核心算法是 最长公共子序列(LCS, Longest Common Subsequence)。其基本思想是:找出两个序列中相同的最长子序列,其余部分即为差异。

例如:

A: a b c d f g h j q z
B: a b c d e f g i j k r x y z

LCS 是:a b c d f g j z

那么差异就是:

  • B 中插入了 e, i, k, r, x, y
  • A 中缺失这些元素

在文本比较中,每一行被视为一个“字符”,然后应用 LCS 算法。

优化技巧与最佳实践

1. 使用colordiff增强可读性

安装 colordiff 后,差异会高亮显示:

sudo apt install colordiff
colordiff -u file1.txt file2.txt

红色表示删除,绿色表示新增 —— 一目了然!

2. 结合grep过滤特定差异

diff -u file1.txt file2.txt | grep "^+" | grep -v "^\++"  # 只看新增行,排除 +++ 行头

3. 使用wdiff对比单词级别差异

sudo apt install wdiff
wdiff -n file1.txt file2.txt

输出中会用 [-删除词-]{+新增词+} 标记。

4. 与git结合使用

虽然 git diff 更强大,但在非仓库目录中,原生 diff 仍是首选:

git diff --no-index file1.txt file2.txt

5. 输出重定向与静默模式

在脚本中避免干扰输出:

diff -q file1.txt file2.txt > /dev/null 2>&1 && echo "相同" || echo "不同"

自定义 Diff 工具链

你完全可以构建自己的“智能 diff”工具链。比如下面这个 Bash 函数:

smartdiff() {
    if [ $# -ne 2 ]; then
        echo "Usage: smartdiff file1 file2"
        return 1
    fi

    if [ ! -f "$1" ] || [ ! -f "$2" ]; then
        echo "Error: 文件不存在"
        return 1
    fi

    echo "🔍 正在比较 $1 与 $2 ..."
    echo "📏 文件大小:$(stat -c%s "$1") vs $(stat -c%s "$2") 字节"

    if command -v colordiff >/dev/null 2>&1; then
        colordiff -u "$1" "$2" | less -R
    else
        diff -u "$1" "$2" | less
    fi
}

保存到 ~/.bashrc,执行 source ~/.bashrc 后即可使用:

smartdiff config.old config.new

在自动化系统中的应用

CI/CD 中的差异检查

# .gitlab-ci.yml 示例片段
stages:
  - test

check-config-changes:
  stage: test
  script:
    - cp config/default.conf config/test.conf
    - sed -i 's/debug=false/debug=true/' config/test.conf
    - if diff -q config/default.conf config/test.conf; then
        echo "❌ 配置未按预期修改!"
        exit 1
      else
        echo "✅ 配置已正确修改。"
      fi

日志监控告警

#!/bin/bash
LOG1="/var/log/app_$(date -d yesterday +%Y%m%d).log"
LOG2="/var/log/app_$(date +%Y%m%d).log"

if [ -f "$LOG1" ] && [ -f "$LOG2" ]; then
    ERRORS_NEW=$(diff "$LOG1" "$LOG2" | grep "^>" | grep -i "error\|exception" | wc -l)
    if [ $ERRORS_NEW -gt 0 ]; then
        echo "🚨 发现 $ERRORS_NEW 条新错误日志!"
        # 触发告警逻辑...
    fi
fi

总结与展望

Linux diff 命令虽小,却蕴含着强大的文本处理能力。从简单的两行对比,到复杂的目录递归、忽略规则、格式定制,它几乎能满足所有日常差异分析需求。而在 Java 世界中,通过引入 java-diff-utils 等专业库,我们也能在程序内部实现同样精准的比较逻辑。

无论是系统管理员排查配置变更,还是开发者做单元测试断言,亦或是 DevOps 工程师编写自动化脚本,掌握 diff 的精髓都能让你事半功倍。

未来,随着 AI 辅助编程的发展,也许我们会看到更“语义化”的 diff 工具 —— 不仅能告诉你“哪行变了”,还能解释“为什么这样变更好”。但在那之前,扎实掌握经典工具,依然是每个工程师的必修课。

附录:完整 Java 工具类封装

下面是一个完整的 Java Diff 工具类,封装了常用操作:

import difflib.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Java Diff 工具类,模拟 Linux diff 命令功能
 */
public class DiffTool {

    /**
     * 读取文件为行列表
     */
    public static List<String> readFileLines(String filePath) throws IOException {
        return Files.readAllLines(Paths.get(filePath));
    }

    /**
     * 生成统一格式差异报告
     */
    public static String generateUnifiedDiff(String originalFile, String revisedFile) throws IOException {
        List<String> original = readFileLines(originalFile);
        List<String> revised = readFileLines(revisedFile);
        return generateUnifiedDiff(original, revised, originalFile, revisedFile);
    }

    /**
     * 生成统一格式差异报告(内存中字符串列表)
     */
    public static String generateUnifiedDiff(List<String> original, List<String> revised,
                                           String originalName, String revisedName) {
        Patch<String> patch = DiffUtils.diff(original, revised);
        StringBuilder sb = new StringBuilder();

        sb.append("--- ").append(originalName).append("\n");
        sb.append("+++ ").append(revisedName).append("\n");

        for (Delta<String> delta : patch.getDeltas()) {
            Chunk<String> orig = delta.getOriginal();
            Chunk<String> rev = delta.getRevised();

            int startOrig = orig.getPosition() + 1;
            int sizeOrig = orig.size();
            int startRev = rev.getPosition() + 1;
            int sizeRev = rev.size();

            sb.append(String.format("@@ -%d,%d +%d,%d @@\n", startOrig, sizeOrig, startRev, sizeRev));

            for (String line : orig.getLines()) {
                sb.append("-").append(line).append("\n");
            }

            for (String line : rev.getLines()) {
                sb.append("+").append(line).append("\n");
            }
        }

        return sb.toString();
    }

    /**
     * 判断两个文件是否相同
     */
    public static boolean filesAreIdentical(String file1, String file2) throws IOException {
        List<String> lines1 = readFileLines(file1);
        List<String> lines2 = readFileLines(file2);
        return lines1.equals(lines2);
    }

    /**
     * 生成简洁报告(类似 diff -q)
     */
    public static String generateBriefReport(String file1, String file2) throws IOException {
        if (filesAreIdentical(file1, file2)) {
            return "Files are identical.";
        } else {
            return String.format("Files %s and %s differ.", file1, file2);
        }
    }

    /**
     * 主方法:命令行接口
     */
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("Usage: java DiffTool [-u] file1 file2");
            System.out.println("  -u : unified format (default)");
            System.out.println("  -q : brief format");
            return;
        }

        try {
            String file1 = args[args.length - 2];
            String file2 = args[args.length - 1];
            boolean unified = true;

            if (args.length > 2 && "-q".equals(args[0])) {
                unified = false;
            }

            if (unified) {
                System.out.print(generateUnifiedDiff(file1, file2));
            } else {
                System.out.println(generateBriefReport(file1, file2));
            }

        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
            System.exit(1);
        }
    }
}

编译运行:

javac -cp ".:lib/*" DiffTool.java
java -cp ".:lib/*" DiffTool -u file1.txt file2.txt

这个工具类让你在 Java 项目中也能享受类似 diff 命令的便利!

无论你是终端高手还是 Java 开发者,掌握 diff 的艺术都将极大提升你的工作效率。拿起键盘,开始你的差异探索之旅吧!

以上就是Linux使用diff命令对比文件内容差异的详细内容,更多关于Linux diff对比文件内容差异的资料请关注脚本之家其它相关文章!

相关文章

  • 浅谈Flink容错机制之作业执行和守护进程

    浅谈Flink容错机制之作业执行和守护进程

    Flink容错机制主要有作业执行的容错以及守护进程的容错两方面,前者包括Flink runtime的ExecutionGraph和Execution的容错,后者则包括JobManager和TaskManager的容错
    2021-06-06
  • Linux下安装grafana并且添加influxdb监控的方法

    Linux下安装grafana并且添加influxdb监控的方法

    这篇文章主要介绍了Linux下安装grafana并且添加influxdb监控的方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-12-12
  • telnet Connection refused端口不通如何处理

    telnet Connection refused端口不通如何处理

    本文介绍了telnet命令的基本用途及排查telnet连接拒绝的处理思路,telnet主要用于测试网络连接,如遇到连接问题,可能是由于防火墙未开放或目的主机服务未启动,文章通过实际例子解释了telnet命令的作用,并提供了解决网络连接问题的方法
    2024-10-10
  • 详解Linux系统中如何修改时间和时区

    详解Linux系统中如何修改时间和时区

    当我们在使用Linux系统时,有时会遇到系统时间与当地时间存在差异的情况,所以这篇文章就给大家详细介绍如何解决这个问题,感兴趣的小伙伴跟着小编一起来学习吧
    2023-07-07
  • linux如何启动openoffice服务

    linux如何启动openoffice服务

    这篇文章主要介绍了linux如何启动openoffice服务问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-09-09
  • Ubuntu Server 14.04升级Ubuntu Server 16.04

    Ubuntu Server 14.04升级Ubuntu Server 16.04

    这篇文章主要介绍了 Ubuntu Server 14.04升级Ubuntu Server 16.04,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2016-12-12
  • Linux中如何配置GRE隧道

    Linux中如何配置GRE隧道

    这篇文章主要介绍了Linux中如何配置GRE隧道问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 阿里云ubuntu16.04如何搭建pptpd服务

    阿里云ubuntu16.04如何搭建pptpd服务

    这篇文章主要介绍了阿里云ubuntu16.04搭建pptpd,需要的朋友可以参考下
    2019-11-11
  • Linux使用dd或fallocate生成指定大小文件的几种方法

    Linux使用dd或fallocate生成指定大小文件的几种方法

    在Linux中,可以使用dd命令生成一个指定大小的文件,包括全零文件、随机数据文件等,使用fallocate命令可以快速生成指定大小的文件,但只分配磁盘空间而不写入数据,本文小编为大家详细说说
    2026-01-01
  • Linux SSH 安全策略 限制 IP 登录方法

    Linux SSH 安全策略 限制 IP 登录方法

    Linux SSH 安全策略 限制 IP 登录方法,使用linux的朋友可以参考下
    2012-07-07

最新评论