我有一个模式文件,我想将其与文件目录进行比较。
模式文件内容看起来像这样(并且可以是正则表达式):
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:为了获得最大速度,请将最罕见的模式放在模式文件的首位,将最常见的模式放在最后,以便尽早消除尽可能多的文件。