Grep 匹配模式文件中的所有项目

Grep 匹配模式文件中的所有项目

我有一个模式文件,我想将其与文件目录进行比较。

模式文件内容看起来像这样(并且可以是正则表达式):

pattern-that-occurs-in-file
pattern-that-also-occurs-in-file

如果内容与模式匹配,则应出现的示例搜索文件:

unrelated content
pattern-that-occurs-in-file
more unrelated content
pattern-that-also-occurs-in-file
further unrelated content

或者:

unrelated content
pattern-that-also-occurs-in-file
more unrelated content
pattern-that-occurs-in-file
further unrelated content

示例搜索文件应该不是过来:

unrelated content
more unrelated content
pattern-that-occurs-in-file
further unrelated content

或者:

unrelated content
pattern-that-also-occurs-in-file
more unrelated content
further unrelated content

或者:

unrelated content
more unrelated content
further unrelated content

我需要 grep 输出出现两种模式的文件列表。我不在乎是否能看到匹配的线条。

我可以用单个命令来完成此操作吗?如果是这样,怎么办?

答案1

不完全是一个命令,而是:

num_patterns=$( wc -l < patterns_file )
for file in dir/*; do
    num_occurrances=$( grep -F -o -f patterns_file "$file" | sort -u | wc -l )
    if (( num_patterns == num_occurrances )); then
        echo "all patterns in $file"
    fi
done

当模式是正则表达式时,此方法将不起作用,因为匹配文本对于每个匹配可能不是唯一的。

答案2

假设./*.txt匹配您感兴趣的所有文件,并且您想要查找包含以下内容的文件全部字符串在文件中./patterns(可能包含两行以上):

#!/bin/bash

pathnames=( ./*.txt )

while IFS= read -r pattern; do
    for pathname in "${pathnames[@]}"; do
        pathnames=( ${pathnames[@]:1} )

        if grep -qF -e "$pattern" "$pathname"; then
            pathnames+=( "$pathname" )
        fi
    done
done < ./patterns

printf 'Matched: %s\n' "${pathnames[@]}"

这会循环模式。对于每个模式,它都会针对数组中的所有文件进行测试pathnames。如果模式匹配,我们将当前路径名保留在数组中,否则将其丢弃。最后,pathnames将仅包含包含所有模式的路径名。

由于pathnames数组的管理方式,grep随着越来越多的文件被丢弃,对每个模式的调用次数将会减少。

该命令pathnames=( ${pathnames[@]:1} )将从数组中移出第一个(当前)路径名,同时pathnames+=( "$pathname" )在末尾将其再次放回。

该命令grep -qF -e "$pattern" "$pathname"将返回一个真的如果文件$pathname包含 中的字符串,则值$pattern。我们使用-q使grep安静,并使其在与文件中的模式匹配时立即退出。我们用来-F进行字符串比较而不是正则表达式匹配。


只是因为我sh更喜欢简洁的语法而不是命名数组bash,所以这里是上面的变体/bin/sh(位置参数替换了pathnames数组):

#!/bin/sh

set -- ./*.txt

while IFS= read -r pattern; do
    for pathname do
        shift

        if grep -qF -e "$pattern" "$pathname"; then
            set -- "$@" "$pathname"
        fi
    done
done < ./patterns

printf 'Matched: %s\n' "$@"

答案3

如果我理解正确的话,这可能是一个选择(如果我的逻辑合理的话)。这里我假设每个文件上的模式都是唯一的:

grep -R < file_with_patterns . | cut -d':' -f1 | uniq -d

grep如果两个模式匹配,则返回两行,或者仅返回一行或没有。利用这种情况,我们uniq -d只显示文件名的重复结果。

答案4

@glenn-jackman 和 @schrodigerscatcuriosity 的答案未通过正则表达式(OP 修改了问题以也包括正则表达式)。例如,模式1.与文件中的“1a”和“1b”匹配,而模式2.不匹配任何内容,但两种算法都得出文件与两种模式匹配的结论。其次,pattern123匹配“1234”,但是12匹配的pattern 不会导致grep 产生任何额外的输出。两种算法都会得出结论,该文件仅匹配两种模式之一。

@kusalananda 的效果很好,但可能有更有效的解决方案:

files=`find ./*.txt`
while read pattern; do
    files=`echo "$files" | xargs grep -l "$pattern"` || break
done < ./patterns
echo Matched: $files

此解决方案与 @kusalananda 的解决方案类似:它循环遍历模式,删除任何不匹配的文件。但是,此解决方案使用xargs grep -l文件而不是嵌套循环。因此,它大约为每个模式运行一个 grep 进程,而不是每个文件每个模式运行一个 grep 进程,因此它应该快一个数量级。

PS:此解决方案不处理文件名中的空格,而@kusalananda 则可以。但可以轻松修改此解决方案以处理文件名中的空格。如果你的文件名中有空格或其他不好的字符,那么首先,羞愧地低下头,其次,更改

xargs

tr \\n \\0 | xargs -0

我没有将其作为主要解决方案,因为它看起来令人困惑并且与主要问题无关。

PPS:为了获得最大速度,请将最罕见的模式放在模式文件的首位,将最常见的模式放在最后,以便尽早消除尽可能多的文件。

相关内容