使用grep -f查找具有匹配项的模式本身

时间:2018-05-27 04:46:49

标签: bash grep

我试图给grep一个模式文件(通过-f),但我想知道哪些模式匹配搜索文件中的某些内容

例如,给定1.txt

a/(.*)
b/(.*)
b/c/(.*)
b/foo/(.*)
d/(.*)
e/(.*)

2.txt

a/
a/foo/bar/
b/foo/
d/foo/

1.txt中与2.txt中的内容匹配的模式(省略(.*)后缀)如下:

a/
b/
b/foo/
d/

我怎样才能找到匹配的模式列表"?

编辑:我只是在寻找前缀匹配,但我认为这个问题对于一般模式匹配非常有趣。

编辑:由于给出了一个基于for循环的解决方案,我应该说我不是在考虑调用grep 10000次。 :)我已经拥有的工作解决方案(下面列出)非常慢:

for line in "${file1_arr[@]}"; do
  if ! grep -qE "^$v(.*)\$"; then
    echo "$line"
  fi
done

理想情况下,我会以较少的开销寻找单个grep电话。

4 个答案:

答案 0 :(得分:2)

在awk中:

$ awk 'NR==FNR{a[$0]=FNR;next}{for(i in a)if($0 ~ i)print i,$0}' 1.txt 2.txt
a/(.*) a/
a/(.*) a/foo/bar
b/(.*) b/foo
d/(.*) d/foo

说明:

$ awk '                 # yes
NR==FNR {               # process first file
    a[$0]=FNR           # hash regex, store record number just in case
    next                # process next record
}
{                       # process second file
    for(i in a)         # loop every entry in 1.txt
        if($0 ~ i)      # if regex matches record 
            print i,$0} # print all matching regex and record
' 1.txt 2.txt           

修改:要输出每个正则表达式一次(如预期输出中显示的here),一旦使用了delete,您就可以a正则表达式$ awk ' NR==FNR { a[$0]; next } { for(i in a) if($0 ~ i) { print i delete a[i] # deleted regex wont get matched again } }' 1.txt 2.txt vendor/cloud.google.com/go/compute/metadata/(.*)$ vendor/cloud.google.com/go/compute/(.*)$ vendor/cloud.google.com/go/(.*)$ vendor/cloud.google.com/(.*)$ vendor/github.com/Azure/azure-sdk-for-go/arm/dns/(.*)$ vendor/github.com/Azure/azure-sdk-for-go/arm/(.*)$ vendor/github.com/Azure/azure-sdk-for-go/(.*)$ vendor/github.com/Azure/(.*)$ vendor/github.com/(.*)$ ,这样就不会多次匹配和输出:

file1.txt

此外,我的测试显示大约60%的折扣(迷你笔记本电脑,1:16到29秒)GNU awk的修改时间(使用您在评论中提供的数据{{1 }和file2.txt):

$ awk '
BEGIN {
    FS="."                   # . splits the url
}
NR==FNR { a[$1][$0]; next }  # we index on the first part of url
{
    for(i in a[$1])          # search space decreased
        if($0 ~ i) {
            print i
            delete a[$1][i]
    }
}' file1.txt file2.txt

通过使用直到第一个句点的字符串的开头作为哈希的键,加速减少了搜索空间,即:

FS="."                                           # split at first .
...
a[vendor/github][vendor/github.com/Azure/(.*)$]  # example of a hash
...
for(i in a[$1])                                  # search space decreased

现在它不必在整个哈希中搜索匹配的正则表达式。更多可能是使用FS="/" ; a[$1 FS $2],但这只是一个快速测试。

答案 1 :(得分:1)

以下脚本:

#!/usr/bin/env bash                                                               

lines=$(wc -l < 1.txt)                                                          
for (( i=1; i<=$lines; i++ )); do                                               
    line=$(sed -n "$i"p 1.txt)                                                  
    line=$(sed "s/\/(.*)$//" <<< "$line")                                       
    grep -E "$line" 2.txt 1>/dev/null && echo "$line"                           
done                                                                            

1.txt中打印与2.txt匹配的行

a                                                                               
b                                                                               
b/foo                                                                           
d                                                                               

注释:

# gets a single line from 1.txt
line=$(sed -n "$i"p 1.txt)                                                  

# removes trailing pattern /(.*) from $line variable
line=$(sed "s/\/(.*)$//" <<< "$line")

# if $line matches in 2.txt, print $line
grep -E "$line" 2.txt 1>/dev/null && echo "$line"                           

答案 2 :(得分:0)

我没有看到grep的解决方案,但sedawk的替代方案。 使用sed我希望在1.txt中看到b/foo/.*等模式,但我会根据(.*)显示解决方案。
第一个命令的目的是构造sed构造,当它与正则表达式匹配时,将用正则表达式替换输入行。不同的输出行必须看起来像

sed -rn 's#b/c/(.*)#b/c/#p' 2.txt

这可以用

完成
# Use subprocess 
sed 's/\(.*\)\(([.][*])\)/s#\1\2#\1#p/' 1.txt
# resulting in
sed -rnf <(sed 's/\(.*\)\(([.][*])\)/s#\1\2#\1#p/' 1.txt) 2.txt| sort -u

解决方案有点难以阅读,导致了1.txt的布局,我想要b/foo/.*这样的行。

以上命令将有2个错误:

当匹配位于线的一部分时,不匹配的部分将显示在输出中。这可以通过匹配垃圾

来修复
# Use lines like 's#.*b/foo(.*)#b/foo#p'
sed -rnf <(sed 's/\(.*\)\(([.][*])\)/s#.*\1\2#\1#p/' 1.txt) 2.txt| sort -u

第二个错误是2.txt中有两个匹配项的字符串只匹配一次(第一个匹配将编辑流中的行)。
这可以通过为匹配行添加一些唯一标记(我将使用\a)并在输出上重复输入行(使用\n&)来修复。 可以通过查找\a标记来查看输出。

sed -rnf <(sed 's/\(.*\)\(([.][*])\)/s#.*\1\2#\\a\1\\n\&#p/' 1.txt) 2.txt| 
sed -rn '/\a/ s/.(.*)/\1/p' | sort -u

编辑:
当您采用不同的方法时,不需要使用标记解决并恢复原始输入 在sed中,您可以在不更改流的情况下将某些内容打印到标准输出 一种可能性(这种情况很慢)是使用

sed '/something/ eecho "something" '

另一种可能性是使用&#34; x&#34;命令(使用保持缓冲区交换模式空间)。你实际上想要一个sed脚本,其中包含

之类的命令
\%a/% {h;s%.*%a/%p;x}
\%b/% {h;s%.*%b/%p;x}
\%b/c/% {h;s%.*%b/c/%p;x}
\%b/foo/% {h;s%.*%b/foo/%p;x}
\%d/% {h;s%.*%d/%p;x}
\%e/% {h;s%.*%e/%p;x}

使用上述方法,sed解决方案简化为

sed -nf <(
   sed 's#([.][*])##; s#.*#\\%&% {h;s%.*%&%p;x} #'  1.txt
   ) 2.txt | sort -u

如果不经常更改文件1.txt,您可能需要预处理该文件。

sed 's#([.][*])##; s#.*#\\%&% {h;s%.*%&%p;x} #'  1.txt > /tmp/sed.in
sed -nf /tmp/sed.in 2.txt | sort -u

答案 3 :(得分:0)

我尝试了基于awksed的解决方案,并且我意识到如果我在内存中读取这两个文件,我可以使用bash的内置regexp引擎更快地完成这项工作。

基本上就是这样。

text="$(cat 2.txt)"                   # read 2.txt

while read -r line; do                # for each 'line' from 1.txt
    re=[^\b]*${line}                  # prepend ^ or \b to the pattern
    if [[ "$text" =~ $re ]]; then     # match the pattern to 2.txt
        echo "${line}"                # if there's a match, print the pattern
    fi
done < <(cat "1.txt")

由于这不会产生任何额外的进程,只是在内存中,我怀疑这是非常有效的。我在詹姆斯答案下链接的文件的基准测试显示8-9秒。