Linux命令行下的PDF比较

37

我正在寻找一款Linux命令行工具,用于比较两个PDF文件并将差异保存到PDF输出文件中。该工具应该能够批处理创建diff-pdf。由于PDF文件是建筑设计图纸,因此纯文本比较无法完成。

类似这样的:

<tool> file1.pdf file2.pdf -o diff-out.pdf

我发现大部分工具都将PDF转换为图像并在GUI中进行比较。

其他任何解决方案也欢迎。


3
这不是一个编程问题,你在寻求一种现有的工具来完成一个任务。 - Raoul
两个构建计划之间的“差异”会是什么样子?这听起来太依赖内容,以至于一般比较程序无法进行“差异化”。 - Noufal Ibrahim
5个回答

55
我编写了一段类似于您所要求的脚本。该脚本使用4个工具来实现其目标:
  1. ImageMagick 的 compare 命令
  2. pdftk 实用程序(如果您有多页 PDF)
  3. Ghostscript(可选)
  4. md5sum(可选)
很容易将其移植到 DOS/Windows 的 .bat 批处理文件中。
但首先,请注意:这仅适用于具有相同页面/媒体大小的 PDF。比较是在两个输入 PDF 之间以像素为单位进行的。结果文件是一个显示“差异”的图像,如下所示:
  • 每个保持不变的像素都变为白色。
  • 每个发生更改的像素都会被涂成红色。
该差异图像保存为新的 PDF,以便在不同的操作系统平台上更好地访问。
例如,我使用它来发现 PDF 处理中出现字体替换时的最小页面显示差异。
可能会出现这样的情况,即您的 PDF 没有可见的差异,但它们在 MD5 哈希和/或文件大小上是不同的。在这种情况下,“差异”输出 PDF 页面会变成全白色。您可以自动发现这种情况,因此只需要通过自动删除所有白色 PDF 来可视化调查非白色 PDF。
以下是构建块:

pdftk

使用此命令行实用程序将多页 PDF 文件拆分为多个单页 PDF:
pdftk  file_1.pdf  burst  output  somewhere/file_1---page_%03d.pdf
pdftk  file_2.pdf  burst  output  somewhere/file_2---page_%03d.pdf

如果你只比较单页PDF,那么这个构建块是可选的。考虑到你谈论的是“施工计划”,很可能是这种情况。

比较

使用来自ImageMagick的命令行实用程序创建每个页面的“diff” PDF 页面:

compare \
       -verbose \
       -debug coder \
       -log "%u %m:%l %e" \
        somewhere/file_1---page_001.pdf \
        somewhere/file_2---page_001.pdf \
       -compose src \
        somewhereelse/file_1--file_2---diff_page_001.pdf

Ghostscript

由于自动插入的元数据(例如当前日期+时间等),PDF输出在基于MD5哈希的文件比较中效果不佳。

如果您想要自动发现所有差异PDF仅由纯白页组成的情况,您应该使用输出设备将PDF页面转换为无元数据的位图格式。您可以像这样执行:

首先,找出您的PDF的页面大小格式。同样,这个小工具identify作为任何ImageMagick安装的一部分提供:

 identify \
   -format "%[fx:(w)]x%[fx:(h)]" \
    somewhereelse/file_1--file_2---diff_page_001.pdf

您可以像这样将此值存储在环境变量中:

 export my_size=$(identify \
   -format "%[fx:(w)]x%[fx:(h)]" \
    somewhereelse/file_1--file_2---diff_page_001.pdf)

现在Ghostscript登场,使用包含上述发现的页面大小变量的命令行:

 gs \
   -o somewhereelse/file_1--file_2---diff_page_001.ppm \
   -sDEVICE=ppmraw \
   -r72 \
   -g${my_size} \
    somewhereelse/file_1--file_2---diff_page_001.pdf

这将为您提供一个分辨率为72 dpi的PPM(Portable PixMap),其基于原始PDF页面生成。72 dpi通常足够满足我们的需求...接下来,创建一个纯白色的PPM页面,其大小与原始页面相同:

 gs \
   -o somewhereelse/file_1--file_2---whitepage_001.ppm \
   -sDEVICE=ppmraw \
   -r72 \
   -g${my_size} \
   -c "showpage"

-c "showpage"是一个PostScript命令,它告诉Ghostscript仅发出一个空白页面。

MD5校验和

使用MD5哈希来自动比较原始PPM和白页PPM。如果它们相同,您可以安全地假定PDF之间没有差异,因此可以重命名或删除diff-PDF:

 MD5_1=$(md5sum somewhereelse/file_1--file_2---diff_page_001.ppm | awk '{print $1}')
 MD5_2=$(md5sum somewhereelse/file_1--file_2---whitepage_001.ppm | awk '{print $1}')

 if [ "x${MD5_1}" == "x${MD5_2}" ]; then 
     mv  \
       somewhereelse/file_1--file_2---diff_page_001.pdf \
       somewhereelse/file_1--file_2---NODIFFERENCE_page_001.pdf # rename all-white PDF
     rm  \
       somewhereelse/file_1--file_2---*_page_001.ppm            # delete both PPMs
 fi

这样可以避免您需要视觉检查那些没有任何差异的“diff PDF”。


非常酷,谢谢你。实际上我还没有找到任何直接进行pdf比较的工具。它们都使用pdf导出为图像文件再进行比较。我会尝试这个方法。 - Christof Aenderl
1
好主意!这里还有更多。要验证您的文件(或其中一页)是否为纯白色,可以使用直方图:convert file.pdf -format %c histogram:info: | grep -Ev '^$|255,255,255,' > /dev/null 如果页面上除了白色以外还有其他颜色,则会返回 0。您也可以使用订阅直接使用 compare 比较 PDF 页面:compare file1.pdf[42] file2.pdf[42] -compose src output.png。现在全部在一起:如果这些 PDF 中的第 i 页不同,则 compare "$src[$i]" "$dst[$i]" -compose src -format %c histogram:info: | grep -Ev '^$|255,255,255,' > /dev/null 将给您成功。 - Yorik.sar
谢谢,我刚看到这个,你帮了我很多。 - jackcogdill
@yentup:您在输出中看到 255,255,25565535,65535,65535 取决于您安装的ImageMagick/compare版本。运行 compare -version 并检查返回的版本字符串中是否有 Q8Q16。 Q16适用于ImageMagick进行每个颜色分量每像素16位处理,而Q8适用于每个像素8位处理... - Kurt Pfeifle
6
你自己的脚本可以在某个地方下载吗?例如作为一个gist? - Ayrat
显示剩余2条评论

40

这里有一个技巧可以实现。

pdftotext file1.pdf
pdftotext file2.pdf
diff file1.txt file2.txt

4
谢谢,但PDF文件是建筑图纸,上面没有文字。 - Christof Aenderl
3
以下是与答案中列出的相同命令,但在一行中:vimdiff <(pdftotext /path/to/pdf-file-1 -) <(pdftotext /path/to/pdf-file-2 -) - joker

5

使用(万能的)ImageMagick和pdftk仅需2行即可完成:

compare -verbose -debug coder $PDF_1 $PDF_2 -compose src $OUT_FILE.tmp
pdftk $OUT_FILE.tmp background $PDF_1 output $OUT_FILE

-verbose和-debug选项是可选的。

  • compare会创建一个PDF文件,其中差异部分以红色像素标记。
  • pdftk将diff-pdf与背景PDF_1合并。

14
好的,你提取了我的回答中的核心命令行,并剥离了其余部分。也许你不需要比较多页PDF和一些额外的角落情况,这些情况已经被我的方法覆盖了... - Kurt Pfeifle

3
在2022年,基于将compare直接应用于PDF文件的答案对我不起作用。似乎这个命令不再正确处理PDF文件了。
然而,当应用于PNG文件时,compare确实有效。
我采用了之前答案中的一些片段编写了一个不同的脚本。事实上,是两个略有不同的脚本:ComparePdfs.shComparePdfs2.sh,可在命令行上执行。这两个脚本列在本答案的末尾。
一些注意事项:
这两个脚本是逐页比较两个PDF文件,并且每对页面纯粹以视觉方式进行比较(因为页面被转换为PNG)。因此,这些脚本只对平面文本和平面图形敏感。如果两个PDF文件之间唯一的区别涉及其他类型的PDF内容,例如逻辑结构元素、注释、表单字段、图层、视频、3D对象(U3D或PRC)等,这两个脚本仍将报告这两个PDF相同。
我还没有尝试特别比较PDF文件中的某些“额外”内容。

如何判断两个(PDF或其他格式的)文件内容是否完全相同

我所知道的另一种比较方法是让我们知道两个PDF文件的内容是否在每个方面都是完全相同的,包括各种嵌入式元数据(如创建日期、文档标题(与第一页显示的任何标题无关)、用于创建PDF的程序等)。

这是检查任何两个文件(PDF或其他类型)是否按位完全相同的相同方法。

要做到这一点,您只需计算并比较两个文件的校验和。我也包含了一个名为AreIdentical.sh的脚本,在本问题的最后列出。以下是如何使用它。

假设这两个文件名分别为“my_first_PDF_file.pdf”和“another_PDF_file.pdf”。然后,一旦您在命令行上执行以下操作,输出文本将根据这两个文件是否相同而读取“same”或“different”。
AreIdentical.sh my_first_PDF_file.pdf another_PDF_file.pdf

请注意,当计算校验和时,文件名等信息不会被考虑在内。原因是文件名并未存储在文件本身中,而是存储在文件的目录项中。因此,即使两个文件的文件名不同,它们也可能被认为是相同的;请参见this question。同样,使用ls -l返回的创建日期(而不是PDF嵌入式元数据中的日期)也不会在计算校验和时被考虑在内,原因相同。 如何使用脚本ComparePdfs.shComparePdfs2.sh 我们假设要(纯粹地视觉上)比较的两个PDF文件file1.pdf和file2.pdf位于工作目录中。
例如,假设它们都有4页,并且除第3页外所有页面都相同。 要完全按照OP所要求的执行操作, 在命令行上,我们执行:
ComparePdfs2.sh file1.pdf file2.pdf dif_in_files.pdf

我选择了一个特定的名称dif_in_files.pdf作为输出文件名。执行过程需要一些时间,因为对于每个输入PDF文件,必须将每个单独的页面转换为PNG格式。当前处理的页面会在终端上打印出来。最后,在工作目录中,脚本将生成包含所有差异页面的文件dif_in_files.pdf,任何差异都会用红色突出显示。

如果我们只对查看不同的页面感兴趣,或者只对是否有差异感兴趣,那么我们使用ComparePdfs.sh

在命令行中,我们执行:

ComparePdfs.sh file1.pdf file2.pdf

在终端中,脚本将输出以下内容:
page_001: same
page_002: same
page_003: different
page_004: same

对于那些不同的页面,仅针对这些页面,脚本将创建突出显示差异之处的文件。在上面的示例中,脚本将生成一个名为difference_page_003.png的文件。

ComparePdfs.sh的工作原理

对于两个pdf文件中的每一个,我们使用pdftk将其拆分为单个页面,然后将每个页面转换为PNG格式。现在考虑两个文件的第一页的PNG文件。我们为每个文件创建检验和(我选择使用b2sum完成此操作)。

如果检验和相同,则认为两个文件的第一页相同。

如果检验和不同,则认为两个文件的第一页不同,并使用compare为它们生成一个差异PNG文件。

我们对每个页面都重复此过程。最后,我们删除所有单个页面的.pdf和.png文件,除了差异文件以外。

脚本

这是ComparePdfs2.sh

#!/bin/bash
file_1="$1"
file_2="$2"
outfile="$3"

# here we set the DPI resolution for the pdftoppm command, which will convert PDF to PNG
resolution=150

# bursting the files into individual pages
pdftk  $file_1  burst  output ${file_1%.*}---page_%03d.pdf
pdftk  $file_2  burst  output ${file_2%.*}---page_%03d.pdf

# this will be a string variable in which we collect that names of .png files to be converted to a single .pdf file
DiffFiles=""

# we loop over the individual pages of the first file
for f1 in `echo ${file_1%.*}---`*.pdf 
do 

  # f2 is the name of the PDF of the corresponding page of the second file
  f2="${f1/${file_1%.*}/${file_2%.*}}" 
  
  # 'b' is an auxilliary varable used to create the variable 'page'
  b="${f1/${file_1%.*}---/""}" 
  
  # 'page' hold the current page number, e.g. 'page_003'
  page="${b/.pdf/}" 
  
  # print the current page being processed
  echo -n "$page "
  
  # convert the individual page PDFs to PNGs
  pdftoppm "$f1" "${f1%.*}" -png -r $resolution
  pdftoppm "$f2" "${f2%.*}" -png -r $resolution
  
  # 'g1' and 'g2' are the names of the two PNG files we just created
  g1=${f1%.*}-1.png
  g2=${f2%.*}-1.png 
  
  # create the difference file for this page
  compare "$g1" "$g2" ${outfile%.*}_"$page".png

  # add the latest name of the difference .png file to the DiffFiles variable
  DiffFiles=$DiffFiles""${outfile%.*}_"$page".png" "
done
echo

# convert the .png difference files to a single .pdf file
convert $DiffFiles $outfile

# clean up
rm -f `echo ${file_1%.*}---page_`* `echo ${file_2%.*}---page_`* `echo ${outfile%.*}_page_`* doc_data.txt

这是 ComparePdfs.sh
#!/bin/bash
file_1="$1"
file_2="$2"

# here we set the DPI resolution for the pdftoppm command, which will convert PDF to PNG
resolution=150

# bursting the files into individual pages
pdftk  $file_1  burst  output ${file_1%.*}---page_%03d.pdf
pdftk  $file_2  burst  output ${file_2%.*}---page_%03d.pdf

# we loop over the individual pages of the first file
for f1 in `echo ${file_1%.*}---`*.pdf 
do 
  # f2 is the name of the PDF of the corresponding page of the second file
  f2="${f1/${file_1%.*}/${file_2%.*}}" 
  
  # 'b' is an auxilliary varable used to create the variable 'page'
  b="${f1/${file_1%.*}---/""}" 
  
  # 'page' hold the current page number, e.g. 'page_003'
  page="${b/.pdf/}" 
  
  # convert the individual page PDFs to PNGs
  pdftoppm "$f1" "${f1%.*}" -png -r $resolution
  pdftoppm "$f2" "${f2%.*}" -png -r $resolution
  
  # 'g1' and 'g2' are the names of the two PNG files we just created
  g1=${f1%.*}-1.png
  g2=${f2%.*}-1.png 
  
  # create the checksums for the two PNG files
  B2S_1=$(b2sum "$g1" | awk '{print $1}') 
  B2S_2=$(b2sum "$g2" | awk '{print $1}') 
  
  # now we compare the checksums
  if [ "$B2S_1" = "$B2S_2" ]; then 
       echo "$page: same"; 
  else 
       echo "$page: different"; 
       # if the checksums are different, create a difference PNG image
       compare "$g1" "$g2" difference_"$page".png 
  fi
done

# clean up
rm -f `echo ${file_1%.*}---page_`* `echo ${file_2%.*}---page_`* doc_data.txt

最后,这是AreIdentical.sh

#!/bin/bash
file_1="$1"
file_2="$2"
B2S_1=$(b2sum $file_1 | awk '{print $1}')
B2S_2=$(b2sum $file_2 | awk '{print $1}')
if [ "$B2S_1" = "$B2S_2" ]; then echo "same"; else echo "different"; fi

0

这里有一个完成的脚本,名为“cmppdf”,基于linguisticturn的代码,增加了对比PDF中文本的支持和一些优化:

https://abhweb.org/jima/cmppdf

文档:

 NAME
    cmppdf -- Compare the visual appearance or text of PDF files

 SYNOPSIS
    cmppdf        [-o BASEPATH] [-q] [-d] FILE1 FILE2
    cmppdf --text [-o BASEPATH] [-q] [-d] FILE1 FILE2

 EXIT STATUS
   0  if no differences found
   1  if differences found
   2+ if trouble

 OPTIONS
   -t, --text      Compare the text in the PDFs, ignoring grapical appearance.

   -o, --output BASEPATH

     With this option a "difference file" named BASEPATH_page_NNN.png
     or .txt is created for each page which has differences.
     With visual comparison (the default), the files will be .png images with
     changed parts highlighted in RED.   With text comparison (--text option),
     the files will contain output from the 'diff' command, or if BASEPATH
     is '-' then all diffs are written to stdout.

   --diff diff-option1,diff-option2, ...

     Specify options to pass to the 'diff' command, separated by commas.
     The default is '-u'.  --text is implied by --diff.

   -q, --quiet     Suppress all progress messages

   -d, --debug     Show detailed information about commands run

@linguisticturn:请通过脚本中提供的电子邮件与我联系,以便我能够给予您适当的荣誉!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接