检查一个文件中的所有行是否都存在于另一个文件中的某个位置

13

我把file1作为file2的数据源,现在需要确保file1中的每一行文本都出现在file2中(如果有任何遗漏,找出哪些行)。需要注意的是,虽然file1每行恰好包含一个搜索词,但这些词可以出现在file2任何位置,包括单词中间。如果匹配不区分大小写将很有帮助 - 只要file2中的文本存在即可,无论文本是否大写。

file1中的行包括空格和各种特殊字符,例如--


https://dev59.com/UGUp5IYBdhLWcg3wx5pu#15059445 - CodyChan
3个回答

15
if grep -Fqvf file2 file1; then
    echo $"There are lines in file1 that don’t occur in file2."
fi

Grep选项的含义:

-F, --fixed-strings       PATTERN is a set of newline-separated fixed strings
-f, --file=FILE           obtain PATTERN from FILE
-v, --invert-match        select non-matching lines
-q, --quiet, --silent     suppress all normal output

1
-w 标志将使您更接近匹配确切行,如问题所述。您仍然必须假设您匹配的任何行都不包含也是单词的子字符串。 - 16807

4
你可以尝试。
awk -f a.awk file1 file2

其中 a.awk 是什么

BEGIN { IGNORECASE=1 }
NR==FNR {
    a[$0]++
    next
}
{
    for (i in a) 
        if (index($0,i)) 
            delete a[i]
}

END {
    for (i in a)
        print i
}

嗯,由于某些原因,这在我创建的所有短测试文件中都可以完美运行,但对于我需要它处理的两个大文件却无法正确工作。它还会输出一些确实在file2中的行。 - user2044638
@user2044638 很有趣。你能给出它输出的file2中的行的例子吗? - Håkon Hægland
抱歉,我没有仔细看,因为我看到了一些在 file2 中的行,所以它最终会输出 file1 中的所有行(只是顺序不同)。至于某些示例行,大多数行似乎没有什么特别之处,主要是互联网链接,其中一些只有一个单词。 - user2044638
@user2044638 嗯...但是它在短测试文件中完美运行? - Håkon Hægland
@HåkonHægland:我建议对你的想法进行三个小修改 - (1)a[$0]++ 更改为简单的 a[$0] :: 计数器不是必要的,因为将哈希键设置到数组中已经去重了; (2) 在扫描 file2 之前预先删除 a[""]; (3) 如果 file2 小于 2 GB 左右,则一次性将其全部加载到 $0 中,使用 FS = RS = "^$"。根据 RS 的定义,file1 中的测试项也不能包括 \n,因此通过 index($0, i) 直接检查每个项目是否与完整的 file2 相匹配不会有行交叉风险,并且我们也不会因分割不需要的字段而获得任何好处。 - RARE Kpop Manifesto

2
这篇帖子中最受欢迎的回答——grep -Fqvf file2 file1并不完全正确,它存在一些问题,所有问题都源自一个主要问题:比较方向被反转了。我们使用file2中的每一行来搜索file1,以确保覆盖了file1中的所有行。这符合grep的工作方式,而且很优雅,但实际上并没有解决问题。我是在比较两个软件包列表时发现了这一点——一个列表是pacman -Qqe的输出,另一个是我编译到不同分组中的列表,以简化设置新电脑。我想确保我没有错过任何软件包。
第一个问题非常严重——如果file2包含一个空行,输出将始终为false(即它不会识别缺失的行)。这是因为file2中的空行将匹配file1的每一行。因此,对于以下文件,我们无法正确识别zsh是否缺失于file2中:
file1                        file2

acpi                         acpi
...                          ...
r                            r
...                          ...
yaourt                       yaourt
zsh                          
<EOF>                        <EOF>

$ grep -Fvf file2 file1
[ no output ]

好的,所以我们只需移除空行,对吗?
$ grep -Fv "$(grep -ve '^&' file2)" file1
zsh

太好了!但是现在我们遇到了另一个问题。假设我们从file2中删除yaourt。我们期望的输出结果应该是:

yaourt
zsh

但实际上我们得到的是:
$ grep -Fv "$(grep -ve '^&' file2)" file1
zsh

为什么会这样呢?原因与空行引起的问题相同。在这种情况下,文件2中的r行与文件1中的yaourt匹配。只删除空行只是修复了这个更普遍问题中最明显的情况。除了这里的假阴性外,还有来自未处理OP所说情况的假阳性。
引用:
很重要的一点是,虽然file1每行都方便地有一个搜索项,但这些项可以出现在file2的任何位置,包括单词中间。
这意味着如果ohmyzsh在file2中,那将与file1中的zsh匹配。但这不会发生,因为我们正在搜索file1以查找ohmyzsh,显然,由于它是ohmyzsh的子字符串,zsh不匹配。最后一个例子说明了为什么分类地使用file2的行搜索file1不起作用。但如果我们使用file1的行搜索file2,我们将得到file2中的所有匹配项,但不知道是否对file1的每一行都有匹配项。匹配数量也没有帮助,因为我们可能对于sh(zsh、bash、fish等)有多个匹配项,但对于acpi没有匹配项。
这就是说,这不是一个可以通过O(1) grep解决的问题。您需要使用循环。使用循环,问题就很简单了。
readarray -t terms < file1 # bash
# zsh: terms=("${(@f)$(< file1)}")

for term in "${terms[@]}"; do # I know `do` "should" be on a separate line; bite me
  grep -Fq "$term" file2 ||
    { echo "$term does not appear in file2" && break }
done

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