Only Return the First Match with Grep
We'll go over 2 different solutions that supports multiple matches on different lines and also on the same line.
I think it will help going over the use case I had before we get into the solutions because then we can take a look at the output of grep against a real example and you can see how this feature of grep is useful out in the wild.
If you only care about the solutions, feel free to skip around.
I have a number of example Docker starter apps for Flask, Rails, Django, Node and Phoenix and I keep them up to date regularly.
Updating them means updating each project’s version of Postgres, Redis, the programming language runtime and all package dependencies. Each project also has its own changelog where I document the updates.
I’ve automated 95% of this process with a few shell and Python scripts. I basically run a command and it updates all of the projects, updates the changelog, run tests and commits all of the changes.
For example, here’s a section of the changelog for the Flask project. When we go over the solutions, you can reference this snippet as what’s been grepped against to get the outputs:
# Changelog
All notable changes to this project will be documented in this file.
## Unreleased
### Changed
#### Languages and services
- Update Python to 3.13.1
- Update Node to 22.13.0
- Update Postgres to 17.2
- Update Redis to 7.4.2
## 0.12.0
### Changed
#### Languages and services
- Update Python to 3.12.5
- Update Node to 20.6.1
- Update Postgres to 16.3
- Update Redis to 7.2.5
As you may have guessed, over time some these version numbers change. The unreleased section gets new versions swapped in as I perform the updates but the previously released versions are locked into a specific version and should not change.
This changelog file has many occurrences of the - Update Postgres to <INSERT_VERSION>
line but I wanted my script to only update the first match in
the unreleased section. I didn’t want it to go back and update every instance
of “Update Postgres to <…>” for previous versions.
Technically you could do a find / replace with sed
and not make it a global
replace but my real script takes the return value of grep and does other things
with it too.
# Both Solutions
Both solutions are going to use example grep commands from the above changelog.
Both solutions take advantage of the --max-count | -m
flag from grep which
returns N number of matches. That’s the focus of this post, but you’ll see that
things work slightly different depending on if you have multiple matches on
multiple lines or 1 line.
When your matches are on different lines
$ grep --max-count 1 "^- Update \`Postgres\` to \`.*\`$" CHANGELOG.md
- Update `Postgres` to `17.2`
Using --max-count 1
returned the first one which in my case was the one I
wanted to update in the unreleased section of the changelog. Done!
When your matches are on the same line
Near the top of the changelog we have this sentence “All notable changes to this project will be documented in this file.”. That sentence has the word “this” listed twice.
What if you only wanted to return the first match?
$ grep --max-count 1 "this" CHANGELOG.md
All notable changes to this project will be documented in this file.
As you can see above, this didn’t work. Grep will return the whole line, and if you have color available in your terminal (not above shown) it will highlight both of the “this” matches.
We could tell grep to only return back the matching text within the pattern:
# The --only-matching flag is the long flag name for -o btw
grep --only-matching --max-count 1 "this" CHANGELOG.md
this
this
The above gets us closer to what we want, but it’s not quite there yet.
# The --only-matching flag is the long name for -o btw
grep --only-matching --max-count 1 "this" CHANGELOG.md | head -n 1
this
There we go.
This is another example of where Unix pipes are helpful. Using head
in the
way we did gets the first line of output. Now you can do whatever you need to
do with this first match.
The video below shows using all of the methods covered above.
# Demo Video
Timestamps
- 0:21 – Use case
- 4:40 – First match on different lines
- 6:47 – First match on the same line
When was the last time you used grep this way? Let me know below.