你可以通过控制GNU diff输出中的旧/新/不变行格式来实现这一点:
diff --new-line-format="" --unchanged-line-format="" file1 file2
输入文件应该经过排序才能工作。使用bash(和zsh),您可以使用进程替换<()就地排序:
diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
在上面的新行和未更改的行被抑制,因此只输出更改的行(即在您的情况下删除的行)。你也可以使用一些其他解决方案没有提供的diff选项,比如-i来忽略大小写,或者各种空格选项(-E, -b, -v等)来进行不那么严格的匹配。
解释
选项——new-line-format、——old-line-format和——unchanged-line-format允许您控制diff格式化差异的方式,类似于printf格式说明符。这些选项分别格式化新(添加)、旧(删除)和不变的行。将1设置为空“”可以防止输出这类行。
如果你熟悉统一的差异格式,你可以部分地重新创建它:
diff --old-line-format="-%L" --unchanged-line-format=" %L" \
--new-line-format="+%L" file1 file2
%L说明符是有问题的行,我们在每一行前面加上“+”“-”或“”,如diff -u
(注意,它只输出差异,它缺少每个分组更改顶部的——+++和@@行)。
您还可以使用它来做其他有用的事情,比如用%dn为每行编号。
diff方法(以及其他建议的comm和join)只产生带有排序输入的预期输出,尽管您可以使用<(sort…)来进行排序。下面是一个简单的awk (nawk)脚本(灵感来自Konsolebox的答案中的链接脚本),它接受任意顺序的输入文件,并按照它们在file1中出现的顺序输出缺少的行。
# output lines in file1 that are not in file2
BEGIN { FS="" } # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; } # file1, index by lineno
(NR!=FNR) { ss2[$0]++; } # file2, index by string
END {
for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}
它将file1的全部内容逐行存储在以行数为索引的数组ll1[]中,将file2的全部内容逐行存储在以行数为索引的关联数组ss2[]中。读取两个文件后,遍历ll1并使用in操作符确定file1中的行是否存在于file2中。(如果存在重复项,则diff方法的输出将不同。)
如果文件足够大,存储这两个文件会导致内存问题,那么可以用CPU交换内存,方法是只存储file1,并在读取file2时删除匹配项。
BEGIN { FS="" }
(NR==FNR) { # file1, index by lineno and string
ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) { # file2
if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}
上面的代码将file1的全部内容存储在两个数组中,一个数组的索引是行号ll1[],另一个数组的索引是行内容ss1[]。然后在读取file2时,从ll1[]和ss1[]中删除每个匹配的行。最后输出file1中的其余行,保留原来的顺序。
在这种情况下,对于如上所述的问题,你也可以使用GNU split(过滤是一个GNU扩展),重复运行file1的块,每次完全读取file2:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
注意在gawk命令行中-含义的stdin的使用和位置。这是通过从file1中拆分为每次调用20000行的块来提供的。
对于非GNU系统的用户,几乎可以肯定你可以获得一个GNU coreutils包,包括在OSX上作为Apple Xcode工具的一部分,它提供了GNU diff, awk,尽管只是POSIX/BSD的分裂,而不是GNU版本。