您可以通过控制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的格式说明。这些选项分别格式化新行(添加),旧行(已删除)和未更改的行。将其中一个设置为空“”会阻止此类行的输出。
如果您熟悉统一的diff格式,则可以使用以下方法部分地重新创建它:
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运算符确定file2中是否存在file1中的行。(diff如果有重复项,则该方法的输出将不同。)
如果文件太大而无法存储它们都导致内存问题,则可以通过仅存储file1并在读取file2的过程中删除匹配项来将CPU换为内存。
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[]由行content索引ss1[]。然后在读取file2时,从ll1[]和中删除每个匹配的行ss1[]。最后,输出文件1的其余行,并保留原始顺序。
在这种情况下,与如所陈述的问题,也可以分而治之使用GNU split(过滤是GNU扩展)中,用file1和读取文件2完全每次的块重复运行:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
需要注意的使用和放置-意义stdin上的gawk命令行。这是由splitfile1以每次调用20000行的数据块提供的。
对于非GNU系统的用户,几乎肯定是GNU coreutils软件包可以获取,包括OSX的部分苹果的Xcode工具,它提供了GNU diff,awk虽然只有一个POSIX / BSD split而不是一个GNU版本。
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt