如何在grep中为每个文件匹配一次?

Posted

技术标签:

【中文标题】如何在grep中为每个文件匹配一次?【英文标题】:How to match once per file in grep? 【发布时间】:2012-10-01 15:17:15 【问题描述】:

是否有任何 grep 选项可以让我控制匹配的总数,但在每个文件的第一次匹配时停止?

例子:

如果我这样做 grep -ri --include '*.coffee' 're' . 我得到这个:

./app.coffee:express = require 'express'
./app.coffee:passport = require 'passport'
./app.coffee:BrowserIDStrategy = require('passport-browserid').Strategy
./app.coffee:app = express()
./config.coffee:    session_secret: 'nyan cat'

如果我这样做grep -ri -m2 --include '*.coffee' 're' .,我会得到:

./app.coffee:config = require './config'
./app.coffee:passport = require 'passport'

但是,我真正想要的是这个输出:

./app.coffee:express = require 'express'
./config.coffee:    session_secret: 'nyan cat'

执行-m1 不起作用,因为我为grep -ri -m1 --include '*.coffee' 're' . 得到了这个

./app.coffee:express = require 'express'

尝试不使用 grep,例如这个find . -name '*.coffee' -exec awk '/re/ print;exit' \; 产生:

config = require './config'
    session_secret: 'nyan cat'

更新:如下所述,GNU grep -m 选项处理每个文件的计数,而 BSD grep 的 -m 将其视为全局匹配计数

【问题讨论】:

再看一遍,我在其他任何地方都没有看到你“真正想要”的文本的第一行。你能确认re 的第一行在你的文件中,./app.coffee 吗?是config = 行,还是express = TL;DR: grep -m1 这个问题更适合Unix & Linux。 【参考方案1】:

因此,使用grep,您只需要选项-l, --files-with-matches

所有关于 findawk 或 shell 脚本的答案都不是问题。

【讨论】:

一直以来,我终于意识到我问错了问题。谢谢! 这正是我一直在寻找的,而且绝对是这个问题的最佳答案!谢谢:) 这是最简单的方法。对于懒人,选项-l--files-with-matches 的缩写。所以你不需要两者。 这绝对比公认的答案简单 非常感谢你!绝对同意@Dalker【参考方案2】:

我认为你可以这样做

grep -ri -m1 --include '*.coffee' 're' . | head -n 2

例如从每个文件中选择第一个匹配项,最多选择两个匹配项。

请注意,这需要您的 grep-m 视为每个文件的匹配限制; GNU grep 确实这样做了,但 BSD grep 显然将其视为全局匹配限制。

【讨论】:

-m1 在全球范围内为我停止第一场比赛。无论如何,如果有数百万个匹配项,而我只想要其中的 100 个,那么这是低效的,因为 grep 在将结果传递到 head 之前仍然会进行前一百万个匹配项 我可以确认,OS X 10.8.2 上的 /usr/bin/grep 是 (BSD grep) 2.5.1-FreeBSD 并且它的 -m 是全局的,而不是每个文件。 GNU grep 是每个文件。 nneonneo,你一定已经用 GNU 工具覆盖了/usr/bin。 @wrick 我建议使用 GNU 工具,OS X 附带的 BSD 工具有点笨拙。从长远来看,这将使您的生活更加轻松。使用MacPorts 或homebrew。 @Graham 仔细检查您是否正在运行 /usr/bin/grep。我找不到任何确定的东西,但我的 10.8.2 机器有 BSD grep 作为 /usr/bin/grep 和 there's a lot of people on the internet confirming。 @Graham 你的测试很好......除了它使用 GNU grep。我们知道 GNU grep 有效。唯一的争论点是 OS X 附带的内容。在 10.8 之前,它是 GNU grep。 10.8 引入了 BSD grep,这在我的机器上以及我之前链接到的所有帖子中都得到了确认。 /usr/bin/grep --versiongrep (BSD grep) 2.5.1-FreeBSDuname -s -rDarwin 12.2.0。您确定您在 OS X 10.8 机器上查看 /usr/bin/grep 并且没有覆盖它吗? 一方面,我从未听说过-m 在全球范围内而不是按文件行事。如果这发生在 OSX 10.8 中,那是苹果主义,与作为 FreeBSD 一部分的 GNU grep 端口无关。 (请注意,如果真的有“BSD grep”这样的东西;它不是来自 FreeBSD。FreeBSD 仍然使用 GNU grep 2.5.1 的端口,就像它(和 OSX)多年来一样。)【参考方案3】:

我会改为在awk 中执行此操作。

find . -name \*.coffee -exec awk '/re/ print FILENAME ":" $0;exit'  \;

如果你不需要递归,你可以用 awk 来做:

awk '/re/ print FILENAME ":" $0;nextfile' *.coffee

或者,如果您使用的是足够电流的 bash,则可以使用 globstar:

shopt -s globstar
awk '/re/ print FILENAME ":" $0;nextfile' **/*.coffee

【讨论】:

未按预期打印。这是我得到的:bash-3.2$ find . -name \*.coffee -exec awk '/re/ print;exit' \; config = require './config' session_secret: 'nyan cat' 一个问题是,至少在我的系统上,您不能真正将find -exec 的输出通过管道传输到head,因为SIGPIPE 进入find 的进程启动,而不是 find 本身,所以它只是在找到两个匹配项后很长时间才重新启动程序。 更新了答案以包含文件名,以及 globstar 作为递归的替代方式。至于到head 的管道,你为什么需要在这里做呢?我在问题中没有看到对此的要求。 awk 脚本负责在每个文件中的第一个匹配项后停止。 @wrick - 只是关于 globstar 的说明;我猜您使用的是较旧的bash,因为您的提示符是base-3.2$。 Globstar 在 4.0 版中被添加到 bash。您可以跳过 globstar,或者使用 MacPorts 安装更新的 bash。另外,我看不出你的输出有问题。虽然 cmets 很讨厌代码/输出格式,但您似乎看到了带有 re 的行。如果您愿意,可以edit your question 为这次尝试添加格式更好的结果。 啊,没错。所以对 OP 最初问题的正确答案是“不”。【参考方案4】:

使用 find 和 xargs。 找到每个 .coffee 文件并对每个文件执行 -m1 grep

find . -print0 -name '*.coffee'|xargs -0 grep -m1 -ri 're'

测试 没有 -m1

linux# find . -name '*.txt'|xargs grep -ri 'oyss'
./test1.txt:oyss
./test1.txt:oyss1
./test1.txt:oyss2
./test2.txt:oyss1
./test2.txt:oyss2
./test2.txt:oyss3

添加-m1

linux# find . -name '*.txt'|xargs grep -m1 -ri 'oyss'
./test1.txt:oyss
./test2.txt:oyss1

【讨论】:

如果文件名中有特殊字符,这将不起作用。见the parsing ls problem。 @Graham 使用find -print0xargs -0,就像我的回答一样,来解决这个问题。 @Graham:容易修改,使用find -print0xargs -0 这个解决方案共享 nneonneo 的问题,它只适用于 GNU grep。 BSD grep 的 -m 是全局的,而不是每个文件。 @Graham 示例请。我不熟悉这个问题。只需使用像 test1?.txt 这样的文件名进行测试就可以了。【参考方案5】:

find . -name \*.coffee -exec grep -m1 -i 're' \;

find 的 -exec 选项为每个匹配的文件运行一次命令(除非您使用 + 而不是 \;,这使它的行为类似于 xargs)。

【讨论】:

【参考方案6】:

您可以在 perl 中轻松做到这一点,而且不会出现乱七八糟的跨平台问题!

use strict;
use warnings;
use autodie;

my $match = shift;

# Compile the match so it will run faster
my $match_re = qr$match;

FILES: for my $file (@ARGV) 
    open my $fh, "<", $file;

    FILE: while(my $line = <$fh>) 
        chomp $line;

        if( $line =~ $match_re ) 
            print "$file: $line\n";
            last FILE;
        
    

唯一的区别是你必须使用 Perl 风格的正则表达式而不是 GNU 风格。 They're not much different.

您可以在 Perl 中使用 File::Find 执行递归部分,或使用 find 提供文件。

find /some/path -name '*.coffee' -print0 | xargs -0 perl /path/to/your/program

【讨论】:

以上是关于如何在grep中为每个文件匹配一次?的主要内容,如果未能解决你的问题,请参考以下文章

如何让 grep 打印每个匹配行下方和上方的行? [复制]

如何使用grep从文件中循环模式[重复]

如何在Linux bash中为每个用户在一行中显示组下的用户

如何在git repo中grep文件?

如何使用grep精确匹配一个单词

如何正确匹配目录与 rsync