Diff/compare 两个文件按文件描述符 (fd) 而不是文件名

Diff/compare two files by file descriptor (fd) instead of file name

在 Linux 中有什么方法可以使用 生成存储在内存中的两个文件的 diff/patch,使用通用格式(即:统一差异,如命令行 diff 实用程序)?

我正在使用一个系统,在该系统中我在内存中生成了两个文本文件,但没有可用或不需要的外部存储。我需要创建这两个文件的逐行差异,并且由于它们是 mmaped,所以它们没有文件名,这使我无法简单地调用 system("diff file1.txt file2.txt").

我有可用的文件描述符 (fds),这是我访问数据的唯一入口点。有什么方法可以通过比较两个打开的文件来生成 diff/patch 吗?如果实现是 MIT/BSD 许可的(即:非 GPL),那就更好了。

谢谢。

在 Linux 上,您可以使用 /dev/fd/ 伪文件系统(符号 link 到 /proc/self/fd)。使用 snprintf() 为两个文件描述符构造路径,例如 snprintf(path1, PATH_MAX, "/dev/fd/%d", fd1); 同上 fd2 和 运行 diff

考虑到需求,最好的选择是实施您自己的 in-memory diff -au。您或许可以根据自己的需要调整 OpenBSD diff 的相关部分。


这里概述了如何通过管道使用 /usr/bin/diff 命令来获取存储在内存中的两个字符串之间的统一差异:

  1. 创建三个管道:I1I2O .

  2. 分叉一个 child 进程。

  3. 在child进程中:

    1. 将管道I1I2的读端移到描述符3和4,写端管道 O 到描述符 1.

    2. 关闭 child 进程中那些管道的另一端。打开描述符 0 以从 /dev/null 读取,并打开描述符 2 以写入 /dev/null.

    3. 执行execl("/usr/bin/diff", "diff", "-au", "/proc/self/fd/3", "/proc/self/fd/4", NULL);

      这会在 child 进程中执行 diff 二进制文件。它将从两个管道 I1I2 读取输入,并将差异输出到管道 O.

  4. parent进程关闭了I1I2管道的读端,并且写入 O 管道的末端。

  5. parent进程将比较数据写入I1I2管道的写端, 并从 O 管道的读取端读取差异。

    注意parent进程必须使用select() or poll()或类似的方法(最好使用非阻塞描述符)来避免死锁。 (如果 parent 和 child 尝试同时读取或同时写入,则会发生死锁。)通常, parent 进程必须不惜一切代价避免阻塞,因为那很可能会导致死锁。

    当输入数据完全写入后,parent进程必须关闭相应的管道写入端,以便child进程检测到end-of-input。 (除非发生错误,否则写结束必须在 child 进程关闭其 O 管道的末端之前关闭。)

    当 parent 进程注意到 O 管道中没有更多数据可用时(read() 返回 0),要么已经关闭了 I1I2 管道的写入端,或者出现错误。如果没有报错,则数据传输完成,可以重新进行child过程。

  6. parent 进程使用例如收获 child waitpid()。请注意,如果有任何差异,diff returns 退出状态为 1.

您可以使用第四个管道从 child 进程接收标准错误流; diff 通常不会向标准错误输出任何内容。

您可以使用第五个管道,在 child 中用 fcntl() 写入标记为 O_CLOEXEC 的结尾,以检测 execl() 错误。 O_CLOEXEC标志表示描述符在执行另一个二进制文件时关闭,因此parent进程可以通过检测读取端的end-of-data来检测diff命令是否成功启动(read() 返回 0)。如果 execl() 失败,child 可以,例如将 errno 值(作为十进制数,或作为 int)写入此管道,以便 parent 进程可以读取失败的确切原因。

整个方法(既记录标准错误又检测执行错误)总共使用了 10 个描述符。这在普通应用程序中应该不是问题,但可能很重要——例如,考虑一个 internet-facing 服务器,其描述符被传入连接使用。