我想搜索两个文本文件并将每个文件中仅在其中一个文件中的行打印到一个文件中。
例如,LIST-1.txt 包含:
apples
dogs
paintings
mom
dad
don
LIST-2.txt 包含
apples
don
dad
mom
cats
我希望输出为(以任何顺序):
dogs
paintings
cats
我已经尝试过这个:
cat list-1.txt | while read line || [[ -n $line ]];
do
grep -v $line list-2.txt
done
有什么建议么?
答案1
您的循环的问题是,在每次迭代中,您都会得到第二个文件中与第一个文件中当前行不同的所有行。
循环的一个变体是以下两个命令的输出的串联:
grep -v -xF -f LIST-1.txt LIST-2.txt
grep -v -xF -f LIST-2.txt LIST-1.txt
第一个grep
将获取与LIST-2.txt
中的任何行不完全对应的所有行LIST-1.txt
,而第二个则grep
对两个文件进行相同的操作。这实际上会给您在问题中提到的结果。 (我看到杰夫已经提到过这一点,所以如果你喜欢这种方法,请投票给他的答案,而不是我的。)
它确实需要将其中一个文件读入内存(作为查询字符串),并且可能会被认为有点不优雅。我也没有真正考虑过在什么情况下它可能无法提供正确的结果。
就我个人而言,我会选择
$ join -v 1 -v 2 <( sort LIST-1.txt ) <( sort LIST-2.txt )
cats
dogs
paintings
这将在文件之间执行关系 JOIN 操作。通常,这将返回两个文件中都存在的条目(内部联接),但在这里我们要求-v 1 -v 2
查看所有条目不在任一文件中匹配。
该join
实用程序需要排序的输入(一次只能在内存中保存每个文件的一行),这就是为什么我们对这两个文件进行排序并join
通过单独的进程替换提供它们。
在没有进程替换的 shell 中,您可能需要在调用之前创建文件的排序副本join
:
sort -o LIST-1.txt.sorted LIST-1.txt &&
sort -o LIST-2.txt.sorted LIST-2.txt &&
join -v 1 -v 2 LIST-[12].txt.sorted
rm -f LIST-[12].txt.sorted
答案2
因为刚刚提到过,但从未明确解释过:GNUcomm
解决方案
comm --output-delimiter '' -3 <(sort file1) <(sort file2)
-3
抑制出现在两个文件中的行,并且分隔符规范将仅左对齐结果。但是,需要对文件进行排序才能comm
正常工作。
答案3
如果您不关心重新排序的结果,一种方法可能是:
sort
两个文件放在一起- 要求
uniq
计算相邻重复项的数量 - 要求
awk
只打印不重复的行 - 要求
cut
删除uniq
的计数
sort list-1.txt list-2.txt | uniq -c | awk '$1 == 1 { print}' | cut -c9-
要强制 grep 执行此操作,您可以在两个方向上运行整行、固定文本、基于文件的排除:
{ grep -vxF -f list-1.txt list-2.txt; grep -vxF -f list-2.txt list-1.txt; }
这要求grep
第二个文件中第一个文件中不存在的行,我们在其中反转第二个文件的文件名grep
。
答案4
如果您的输入文件没有单独的重复条目,您可以使用:
sort list[12] |uniq -u
或使用awk
作为替代:
awk '{ seen[$0]++ } END{ for (x in seen) if (seen[x]==1) print x }' list[12]