ChatGPT解决这个技术问题 Extra ChatGPT

Grep only the first match and stop

I'm searching a directory recursively using grep with the following arguments hoping to only return the first match. Unfortunately, it returns more than one -- in-fact two the last time I looked. It seems like I have too many arguments, especially without getting the desired outcome. :-/

# grep -o -a -m 1 -h -r "Pulsanti Operietur" /path/to/directory

returns:

Pulsanti Operietur
Pulsanti Operietur

Maybe grep isn't the best way to do this? You tell me, thanks very much.


T
Trevor Boyd Smith

-m 1 means return the first match in any given file. But it will still continue to search in other files. Also, if there are two or more matched in the same line, all of them will be displayed.

You can use head -1 to solve this problem:

grep -o -a -m 1 -h -r "Pulsanti Operietur" /path/to/dir | head -1

explanation of each grep option:

-o, --only-matching, print only the matched part of the line (instead of the entire line)
-a, --text, process a binary file as if it were text
-m 1, --max-count, stop reading a file after 1 matching line
-h, --no-filename, suppress the prefixing of file names on output
-r, --recursive, read all files under a directory recursively

I don't think they are necessary (except for -r obviously), but they should not hurt (I would not use -a though)
Exactly what I needed. My pattern was found twice on the same line and grep -m 1 returned both instances because of this. |head -1 solved it!
Does head short circuit once the first match is found?
@Chris_Rands the exact behavior depends on the shell that you are running in. head will exit as soon as it encounters the first line. grep will exit the next time it tries to write after head has exited. Some shells will wait until all elements of a pipeline finish, some will cause the entire pipe to shutdown as soon as the last program in the pipe exits.
@3Qn, I don't understand your comment: first not first from result. This answer prints first match in any file and stops. What else did you expect?
V
Venkat Kotra

You can pipe grep result to head in conjunction with stdbuf.

Note, that in order to ensure stopping after Nth match, you need to using stdbuf to make sure grep don't buffer its output:

stdbuf -oL grep -rl 'pattern' * | head -n1
stdbuf -oL grep -o -a -m 1 -h -r "Pulsanti Operietur" /path/to/dir | head -n1
stdbuf -oL grep -nH -m 1 -R "django.conf.urls.defaults" * | head -n1

As soon as head consumes 1 line, it terminated and grep will receive SIGPIPE because it still output something to pipe while head was gone.

This assumed that no file names contain newline.


I'm trying to adopt this solution to search in a large number of archive files with xargs: find . -name '*.gz' | xargs -I '{}' stdbuf -oL zgrep -al 'pattern' {} | head -n 1. This, however, does not terminate on the first match. Any advice?
Wouldn't grep's --line-buffered option prevent buffer overhead without calling an additional utility?
c
cxw

My grep-a-like program ack has a -1 option that stops at the first match found anywhere. It supports the -m 1 that @mvp refers to as well. I put it in there because if I'm searching a big tree of source code to find something that I know exists in only one file, it's unnecessary to find it and have to hit Ctrl-C.


so you would say that ack is faster than grep? I'm really concerned with the speed factor also.
ack can be faster than grep, depending on what it is you're searching. Please note that ack is about searching source code. If you're looking to search general files, it is less good at that, at least in ack 1.x. Go read about ack and see if maybe it fits your needs.
I've been using Ack for a long while but recently switched to The silver searcher which I find to be faster the Ack
I believe this should be the only answer because the OP said he wanted it done with grep, but the other answer uses head (both work of course) but there are some embedded/self created environments with minimal tools where grep is common and tail/head is not.
Worth mentioning that ag might be fast, but it does not have the -1 option which is useful in this case
4
4b0

You can use below command if you want to print entire line and file name if the occurrence of particular word in current directory you are searching.

grep -m 1 -r "Not caching" * | head -1

K
Katie Byers

For anyone who lands here, as I did, perplexed as to why --max-count didn't seem to be working when acting on stdin...

TL;DR - --max-count n does NOT stop after finding n matches, it stops after finding all matches on n lines.

(And stdin, even if it's only a string, counts as one line.)

This is true despite the fact that, in zsh 5.8, at least, man grep describes the option this way:

-m num, --max-count=num
        Stop reading the file after num matches.

Longer Explanation

In my case, I was trying to grab just the first part of a relative path:

>  echo "some/path/here" | grep -E -o -m 1 '[^\/]+'

and was quite confused when it gave me back

some
path
here

Thanks to the comment from @harperville above, I finally figured out: It's not about the output, it's about the input.

Indeed, when I tried

>  echo "some/path/here\nanother/path/there" | grep -E -o -m 1 '[^\/]+'

I got the same result as above (i.e., only the parts before the \n in this second example).

Notes

For those who are less familiar with grep:

-E (--extended-regexp) tells it to use "extended" regular expressions, i.e., the ones you're used to from most other programming languages. The differences between "extended" and "basic" aren't big - it's just about which characters you need to escape in your regex - but as someone who's primarily a TS and Python developer, I always use -E because that way I never have to think about it. (Pro-tip: Add alias grep="grep -E" to your .zshrc and you'll never have to worry about it again!)

-o (--only-matching) tells it to only print the matches, rather than each line on which it found a match.

-m n (--max-count n) restricts it to searching n lines. (If you've read this far you clearly already know that, though! 😛)


S
Sergio Abreu

Reading the grep manual (man grep) this is the minimum command to find first match with Extended regexp. Example getting the ethernet name that in my laptop is NOT eth0 !

$ ifconfing | grep -E -o -m 1 "^[a-z0-9]+"

Explanation: -E for extended regexp, -o to return only the match, -m 1 to look only one line


Y
Yam Marcovic

A single liner, using find:

find -type f -exec grep -lm1 "PATTERN" {} \; -a -quit

This is going to be very slow, as find will spawn copy of grep for every file found. grep -r works a lot faster - its only one copy that does directory traversals.
True; though find can be customized to only operate on filtered results, which can then make the operation much faster than a catch-all grep. Depends on the context.