Getting the first match with grep

15.1k views Asked by At

I have a bash function that goes through a list of files and greps for a certain word and writes it into a file. The problem is I get every instance of the word, I just want to get the first instance. I came across a solution by adding head -1 with the grep, but now, my function just hangs when I call it.

 83 function processAppLogs {
 84         for i in `find $log_des -name $fname`
 85             do
 86                 p=$i
 87                 d=${p//applog.txt/unmountTimestampList.txt}
 88                 grep "UNMOUNTED"  $i >> $d
 89                 grep "PATIENT ID" | head -1 | $i >> $d
 90             done
 91 }

I'm looking to grep only the first instance of "PATIENT ID" but I think I might have the syntax wrong? Is that the proper way to grep the first instance and write that to a file?

3

There are 3 answers

0
nu11p01n73R On BEST ANSWER

As a side not to @fedorqui's answer.

What you got wrong is the order of commands. The head must occur after the grep and then should the redirection come. Like

grep "PATIENT ID" $i | head -1  >> $d
1
fedorqui On

You can use -m 1 to indicate that you just want to match once:

grep -m 1 "PATIENT ID" >> "$d"

From man grep:

-m NUM, --max-count=NUM

Stop reading a file after NUM matching lines.

Test

$ seq 8 12
8
9
10
11
12
$ seq 8 12 | grep 1 -m 1
10
$ seq 8 12 | grep 1 -m 2
10
11
3
Tom Fenech On

The answer is to use -m, to specify a maximum number of matches, if your version of grep supports it. Otherwise, piping the output to head will work. head will exit after it reaches the number of lines specified, breaking the pipe and causing grep to exit, so the outcome will be the same (albeit at the expense of a pipe).

A portable alternative using a single process would be to use awk:

awk '/PATIENT ID/ {print; exit}' "$i"

Loops like for i in `find $log_des -name $fname` should be avoided, as they rely on well-behaved filenames (no spaces or glob characters present) and break otherwise.

If you're relying on find to do a recursive directory search, then use globstar instead, as it allows you to do what you want safely.

shopt -s globstar
for i in **/"$fname"; do
    d=${i//applog.txt/unmountTimestampList.txt}
    { grep "UNMOUNTED" "$i"; grep -m 1 "PATIENT ID" "$i" } >> "$d"
done

As a bonus I've also redirected a single block, rather than each command separately (perhaps you can use > instead now?) and safely quoted your variables.