如果“file”中的项是空字符分隔的,如何使用“grep -f file”?

6

我需要从大量文件(data2data3,...)中查找在data1中存在的空值分隔项目。需要精确匹配。

使用grep -f data1 data2 data3 ... 可以正常运行,但是如果data1中的项目也是空值分隔的,则无法正常工作。

  1. Using only newlines - ok:

    $ cat data1
    1234
    abcd
    efgh
    5678
    $ cat data2
    1111
    oooo
    abcd
    5678
    $ grep -xFf data1 data2
    abcd
    5678
    
  2. data2 contains null-delimited items - ok when -z used:

    $ printf '1111\0oooo\0abcd\0005678' > data2
    $ grep -zxFf data1 data2 | xargs -0 printf '%s\n'
    abcd
    5678
    
  3. Now both data1 and data2 contain null-delimited items - fail. Seems that the -z option does not apply to the file specified with -f:

    $ printf '1234\0abcd\0efgh\0005678' > data1
    $ grep -zxFf data1 data2 | xargs -0 printf '%s\n'
    
    $
    
问题在于我需要两个文件都是空值分隔符的项目。明显的解决方法可能是(例如)使用一个好老的while循环:
while IFS= read -rd '' line || [[ $line ]]; do
    if grep -zqxF "$line" data2; then
        printf '%s\n' "$line"
    fi
done < data1

但是由于我有许多文件和大量的项目,这将非常缓慢!是否有更好的方法(我不坚持使用grep)?


您需要保留顺序吗? comm 操作是否可以像 grep 一样工作? (并且您是否-z 的 GNU comm?) - Charles Duffy
@CharlesDuffy 排序根本不是问题。 - PesaThe
你是否使用grep是因为你真的需要它的正则表达式匹配速度,还是因为你遇到了一个起初足够简单以便用grep来拼凑解决方案的问题?如果是后者,那么现在是你转向一种具有适当数据结构的语言的时候了,将data读入内存,并从那里开始。 - chepner
2个回答

6

由于订单保留不重要,您需要匹配确切的字符串,并且可以使用GNU工具,因此建议使用comm -z而不是fgrep

$ printf '%s\0' 1111 oooo abcd 005678 >data2
$ printf '%s\0' 1234 abcd efgh 005678 >data
$ comm -z12 <(sort -uz <data) <(sort -uz <data2) | xargs -0 printf '%s\n'
005678
abcd

如果您在首位生成文件(因此可以省略“排序”操作),这也将具有非常好的内存和性能特性。

这个方案可以实现(+1),但由于我有很多文件,我需要多次使用 sortcomm 命令。你有没有想到更有效的解决方案,或者你认为这样就足够快了? - PesaThe
2
@PesaThe,comm非常快速,只要保留它们的结果,您可以在每个文件中执行一次sort而不是每个操作。 - Charles Duffy
@PesaThe,另一件事是——你想用这些文件做什么?如果你想在file1和"file2、file3或file4中的任何一个"中查找项目,你可以使用一个comm调用轻松地比较已合并和排序的file2、file3和file4与file1。建议一个好的解决方案需要更多了解你试图解决的问题。 - Charles Duffy
1
哦,我可以使用 comm ... <(sort -uz data2 data3 ...). 我需要休息一下 :) 谢谢,听起来足够快! - PesaThe
1
如果 data2data3 等都已经单独预先排序好了,你可以使用 comm ... <(sort -umz data2 data3 ...) 来使用归并排序将它们合并起来,这样会更快(而且更省内存)。 - Charles Duffy

2
尽管以下内容可能不是这个特定情况的最佳解决方案,但我还是加入了它,以防将来有类似问题的读者。请参见下面的gawk解决方案,它可能对这种用例有用。 grep已经将换行符硬编码为模式终止符。即使您使用-e pattern,在模式字符串中的换行符也会导致grep将选项处理为指定多个模式而不是包含换行符的单个模式。
但是,如果您的以NUL分隔的模式不包含换行符,则可以使用Gnu xargssed构建适当的grep调用,并使用-e命令行参数。
sed -z 's/^/-e/' data | xargs -0 grep -zF data2 ...

这种方法有效是因为Gnu的grep会重新排列命令行参数,所以将要搜索的文件放在模式之前是可以的。但在许多其他grep实现中无法使用。

据我所知,没有办法处理可能包含换行符的模式。grep -Egrep -F不识别ASCII转义序列,并且会从包含换行符的模式中静默创建多个模式。grep -P(另一个使用PCRE正则表达式的Gnu扩展)将正确处理嵌入的换行符或ASCII转义,但只允许单个模式。


完整的NUL结束的匹配,无需排序

如果您只对精确的完整“行”匹配(-Fx)感兴趣,则可以使用Gnu Awk脚本而不是对输入和模式进行排序。对于无法放入内存的非常大的输入,这可能是一种胜利;使用外部临时文件进行排序可能非常昂贵。Awk解决方案使用哈希表,因此无需排序。(同样,这可能无法在所有Awk上工作,因为它依赖于将RS设置为NUL。)

awk -v RS=`\0` 'NR==FNR{p[$0] = 1; next;} $0 in p' data data2 ...

有趣,所以换行符是不幸的硬连接。我的“模式”可以包含换行符,所以使用了空分隔符。无论如何,这是一篇很好的文章,你可能想说的是< <(sed...)。谢谢! - PesaThe
1
我刚看到你的更新,不得不承认我有点尴尬,因为当我需要查找固定字符串时,我试图寻找一些复杂的grep解决方案,而实际上我可以使用非常简单直观的awk -RS='\0'... - PesaThe
1
我现在也犹豫不决,是否应该更改已接受的答案。两个答案都很出色,您的回答提供了更多信息,但是... - PesaThe
1
@PesaThe:随便吧。Charles和我都不需要声望 :). grep解决方案会有用处,但在实际需要完整行匹配的情况下不适用;我承认,直到现在我才看到你命令行中的“-x”选项。 - rici
1
对于awk来说,如果数据包含大量不匹配“good”列表的值(行),您可以通过测试$0 in p而不是p[$0]/*!=0*/来减少内存膨胀(可能导致溢出)。 - dave_thompson_085
显示剩余3条评论

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