I'd like to filter the content of a log of a pom.xml file using a regex.

I created it on regexr and it works using PCRE; this is my regex:

commit \K[a-z0-9]*(?=[\s\S]*\+.*<version>1.2.0)

I then tried to run with the following commands:

git log --full-history -p pom.xml | grep -P "commit \K[a-z0-9]*(?=[\s\S]*\+.*<version>1.2.0)"


git log --full-history -p pom.xml | perl -nle 'print \$1 if /commit \K[a-z0-9]*(?=[\s\S]*\+.*<version>1.2.0)/'

but neither of them worked (nothing seems to match).

I'm surely missing something, but I cannot figure out what.


To clarify, this is an example of git log:

commit a1357f4e1cb2c34aa1a1357f4e1cb2c34aa1471f
Author: Author <[email protected]>
Date:   Wed Mar 30 15:04:29 2022 +0100
    commit message
diff --git a/pom.xml b/pom.xml
index 93df07e..5f82fd2 100755
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
-       <version>1.1.1</version>
+       <version>1.2.0</version>
        <description>Description of project</description>

I want to pick the hash of the commit where version changed to a certain value (1.2.0 in the regex I wrote).

Obviously, this being the log of all the commits done on a certain repository, there may be more than one commit as well.

Using the aforementioned regex on this input should output the commit hash:


This is what actually happens using regexr.

  • I edited the question to clarify. Aug 3, 2022 at 6:19
  • What's wrong with git log… | awk '$1=="commit" {print $2}'? Presumably I'm missing something so please could you edit your question to clarify Aug 3, 2022 at 11:12
  • I modified the question. Anyway, I already found a solution (below). Aug 3, 2022 at 11:57
  • 1
    "Obviously [..] there may be more than one commit as well." - actually it's not at all obvious from your question unless one is familiar with git log. Perhaps you could edit your question to extend the example to include one (or two) pom.xml commits along with one or two different commits. Aug 3, 2022 at 16:10

3 Answers 3


For the sample commit log saved to the file gitlog, the GNU grep command pulls up

% < gitlog ggrep -Pzo 'commit \K[a-z0-9]*(?=[\s\S]*\+.*<version>1.2.0)'
% < gitlog ggrep -Pzo 'commit \K[a-z0-9]*(?=[\s\S]*\+.*<version>1.2.0)' | od -c
0000000    a   1   3   5   7   f   4   e   1   c   b   2   c   3   4   a
0000020    a   1   a   1   3   5   7   f   4   e   1   c   b   2   c   3
0000040    4   a   a   1   4   7   1   f  \0   m   e   s   s   a   g   e
0000060   \0

which is maybe not ideal, given the false positive. Also, the documentation for the PCRE flag -P mentions something about being experimental with -z. With a line-based regular expression one could use ^commit to limit where the match starts, but -z prevents that, unless GNU grep has a flag to modify where ^ matches like Perl does:

% < gitlog perl -0777 -nE 'say $1 if m/^commit (\S+).*<version>1.2.0/ms'

Here -0777 is a less experimental version of the GNU grep -z flag (-0777 slurps up the whole input), and the ms regular expression flags lets ^ match newlines anywhere and the . to skip over newlines. However, this could be extremely inefficient as the regular expression might have to search the entire log multiple times for each commit and perhaps may not find the desired version number. Or, it could match the commit multiple times, as there is nothing that restricts the version information to only matching with the commit line prior to it.

Another method is to remember the last commit, and use that value when the version number is found. This allows the parse to go line-by-line:

% < gitlog perl -nle 'if (m/^commit (\S+)/) {$commit=$1} if(m/<version>1.2.0/) {print $commit}'

This may instead want {print $commit;exit} to stop searching when a matching version is found, if you do not care about the rest of the input (which may be a lot).

% < gitlog perl -nle 'if (m/^commit (\S+)/) {$commit=$1} if(m/<version>1.2.0/) {print $commit;exit}'

For even more speed this can be written in awk, which I can usually figure out only after I write it in Perl:

% < gitlog awk '/^commit/{c=$2};/<version>1.2.0/{print c;exit}'

Ideally your test input (or inputs) should exercise many different possibilities: multiple commit records, both before and after the target, duplicate records, etc, especially if this code is to be used in any sort of unattented fashion without a human right there to sanity check the results:

% < gitlog
blah blah blah

commit a1357f4e1cb2c34aa1a1357f4e1cb2c34aa1471f

    commit eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

-       <version>1.1.1</version>
+       <version>1.2.0</version>

commit ffffffffffffffffffffffffffffffffffffffff
+       <version>1.2.0</version>
  • Nice answer, but I'm missing something about the first statement: which regex did you provide to grep? Aug 4, 2022 at 5:47
  • 1
    The one in your answer.
    – thrig
    Aug 4, 2022 at 13:10
  • I edited a real commit message just to provide an example, but I didn't realize it actually could lead to a false positive, thank you! Aug 4, 2022 at 13:39
  • Anyway, I resorted to use my solution, but your answer is so detailed that I marked it as the accepted one Aug 4, 2022 at 14:25

Nevermind, I found the solution on my own.

Turns out I was halfway there, but I was missing some details.

As this answer on Stack Overflow explains, it is necessary to pass some other arguments to grep to make it work as expected; also, single quotes were to be used istead of double quotes.

Consequently, the first commands becomes:

git log --full-history -p pom.xml | grep -Pzo 'commit \K[a-z0-9]*(?=[\s\S]*\+.*<version>1.2.0)'


Using Raku (formerly known as Perl_6)

The following code pulls the first line out of a gitlog commit if <version>1.2.0 is found later in the text:

raku -e 'put $_.split("\n")[0] if m/ \<version\>1\.2\.0 / given slurp();' 


raku -e 'put lines[0] if m/ \<version\>1\.2\.0 /;'   


commit a1357f4e1cb2c34aa1a1357f4e1cb2c34aa1471f

You can remove the "commit " text as follows:

raku -e 'put $_.split("\n")[0].subst("commit ") if m/\<version\>1\.2\.0/ given slurp();' 



Escaping rules for Regexes are amazingly simple in Raku: just backslash every non-<alnum> character to have it understood as a literal (i.e. assume non-<alnum>s have special meaning--like . dot representing any character). Or quote the text you're searching for, i.e. m/ "<version>1.2.0" /:

raku -e 'put .split("\n")[0].subst("commit ") if m/"<version>1.2.0"/ given slurp();' 


raku -e 'put .[0].subst("commit ") if m/"<version>1.2.0"/ given lines();'     




You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .