Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Only Return the First Match with Grep

only-return-the-first-match-with-grep.jpg

We'll go over 2 different solutions that supports multiple matches on different lines and also on the same line.

Quick Jump:

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.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per year (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments