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

How to match once per file in grep?

提问人:pathikrit 提问时间:10/11/2012 最后编辑:pathikrit 更新时间:11/27/2018 访问量:59550

问:

是否有任何 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'

做是行不通的,因为我得到了这个-m1grep -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 将其视为全局匹配计数

正则表达式 perl shell unix grep

评论

0赞 Graham 10/11/2012
再看一遍,我在其他任何地方都看不到你“真正想要”的文本的第一行。您能确认您的文件中的第一行是什么吗?是一条线,还是?re./app.coffeeconfig =express =
0赞 Dan Dascalescu 1/15/2020
TL的;DR:这个问题更适合Unix和Linuxgrep -m1

答:

44赞 nneonneo 10/11/2012 #1

我认为你可以做这样的事情

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

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

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

评论

4赞 pathikrit 10/11/2012
-m1对我来说,在全球第一场比赛中停止。无论如何,如果有数百万个匹配项,而我只想要其中的 100 个,那么这是低效的,因为在将结果引入之前,grep 仍然会进行前一百万个匹配项head
5赞 Schwern 10/11/2012
我可以确认,OS X 10.8.2 上的 /usr/bin/grep 是全局的,而不是每个文件。GNU grep 是按文件计算的。nneonneo,你一定被GNU工具覆盖了。@wrick我建议购买 GNU 工具,但 OS X 附带的 BSD 工具有点卡顿。从长远来看,它会让你的生活更轻松。使用 MacPorts自制软件(BSD grep) 2.5.1-FreeBSD-m/usr/bin
1赞 Schwern 10/12/2012
@Graham 仔细检查您正在运行 /usr/bin/grep。我找不到任何确定的东西,但我的 10.8.2 机器将 BSD grep 作为 /usr/bin/grep并且互联网上有很多人确认
1赞 Schwern 10/12/2012
@Graham 你的测试没问题...除了它使用 GNU grep。我们知道 GNU grep 是有效的。唯一的争论点是OS X附带的内容。在 10.8 之前,它是 GNU grep。10.8 引入了 BSD grep,这在我的机器和我之前链接到的所有帖子上都得到了确认。 .您确定您正在 OS X 10.8 机器上查看并且没有覆盖它吗?/usr/bin/grep --versiongrep (BSD grep) 2.5.1-FreeBSDuname -s -rDarwin 12.2.0/usr/bin/grep
2赞 ghoti 10/12/2012
首先,我从未听说过全局行动而不是按文件行事。如果这种情况发生在 OSX 10.8 中,那就是苹果主义,与作为 FreeBSD 一部分的 GNU grep 移植无关。(请注意,如果真的有 “BSD grep” 这样的东西,它不是来自 FreeBSD。FreeBSD 仍然使用 GNU grep 2.5.1 的移植,就像它(和 OSX)多年来一样。-m
4赞 ghoti 10/11/2012 #2

我会这样做。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

评论

0赞 pathikrit 10/11/2012
未按预期打印。这是我得到的:bash-3.2$ find . -name \*.coffee -exec awk '/re/ {print;exit}' {} \; config = require './config' session_secret: 'nyan cat'
0赞 ruakh 10/11/2012
这样做的一个问题是,至少在我的系统上,你不能真正将 的输出传递给 ,因为它会进入启动的进程,而不是它自己,所以它只是在找到两个匹配项后很长一段时间内继续重新启动程序。find -execheadSIGPIPEfindfind
1赞 ghoti 10/11/2012
更新了答案以包括文件名,以及 globstar 作为递归的替代方式。至于管道,你为什么需要在这里这样做?我在问题中没有看到这方面的要求。该脚本负责在每个文件中的第一个匹配项后停止。headawk
1赞 ghoti 10/11/2012
@wrick - 只是关于 globstar 的说明;我发现你正在使用一个较旧的,因为你的提示是.Globstar 在 4.0 版中被添加到 bash 中。您可以跳过 globstar,也可以使用 MacPorts 安装更新的 globstar。另外,我看不出您的输出有问题。虽然注释对于代码/输出格式很糟糕,但您似乎看到其中有行。如果您愿意,可以编辑您的问题,以包含此尝试的格式更好的结果。bashbase-3.2$bashre
1赞 ghoti 10/11/2012
啊,你说得对。因此,OP最初问题的正确答案只是“否”。
0赞 Schwern 10/11/2012 #3

你可以在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风格。它们没有太大区别

你可以在Perl中使用File::Find来做递归部分,或者使用feed文件。find

find /some/path -name '*.coffee' -print0 | xargs -0 perl /path/to/your/program
2赞 oyss 10/11/2012 #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

评论

0赞 Graham 10/11/2012
如果文件名中有特殊字符,这将不起作用。请参阅解析 ls 问题
0赞 Schwern 10/11/2012
@Graham 使用和,就像我的回答一样,来解决这个问题。find -print0xargs -0
0赞 nneonneo 10/11/2012
@Graham:易于修改,使用和。find -print0xargs -0
3赞 Schwern 10/11/2012
这个解决方案分享了 nneonneo 的问题,它只适用于 GNU grep。BSD grep 的 -m 是全局的,而不是每个文件。
0赞 oyss 10/11/2012
请@Graham例。我不熟悉这个问题。只需使用像 test1?这样的文件名进行测试。txt 还是可以的。
2赞 Barmar 10/11/2012 #5

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

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

94赞 fenollp 3/25/2014 #6

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

所有这些关于 或 shell 脚本的答案都远离了这个问题。findawk

评论

0赞 ceiling cat 5/11/2018
这是最简单的方法。对于懒惰者,选项是 的缩写。所以你不需要两者兼而有之。-l--files-with-matches