GNU grep regex for Semver matching

Viewed 378

I have a URL and I am trying to extract a part of the Semver. The Semver is of the format 2.x.0 (it will always be 2.x.0, if that helps). I need the x value, in the case of my example it is 15290.

I am using GNU grep. This is what I have so far:

$echo "https://example.com/repository/local/foo-bar/2.15290.0/foo_bar-2.15290.0.tar.gz" |  grep -oP '(?<=[.])\d+(?=[.0])'

(?<=[.]) is a look behind. It matches, in this case, just after a period.

\d+ matches any number of digits characters.

(?=[.0]) is a look ahead. It matches, in this case, just before a period 0.

The output I am getting is

15290
15290
0

I think the 0 is getting matched because of foo_bar-2.15290.0.tar.gz. Is there a better way of doing this? I am not sure if my logic is sound.

3 Answers

You might extend the lookarounds to be more specific and match for example the following /

(?<=\d[.])\d+(?=\.\d+/)

Regex demo

echo "https://example.com/repository/local/foo-bar/2.15290.0/foo_bar-2.15290.0.tar.gz" |  grep -oP '(?<=\d[.])\d+(?=\.\d+/)'

Output

15290

If the / is not always present, you could also assert a . and digits to the right, not followed by a . again

(?<=\d[.])\d+(?=\.\d+(?!\.))

Regex demo

Your regex, (?<=[.])\d+(?=[.0]), matches any one or more digits that are preceded with a dot and followed with a dot or 0. The 2.15290.0/foo_bar-2.15290.0. part of the string thus has three valid matches, 15290, 15290 and 0.

You can use

grep -oP '.*2\.\K\d+(?=\.0\b)' <<< "https://example.com/repository/local/foo-bar/2.15290.0/foo_bar-2.15290.0.tar.gz"

See the regex demo and the online grep demo. Details:

  • .* - any zero or more chars other than line break chars, as many as possible (this makes grep return the last occurrence of the match)
  • 2\. - 2. string
  • \K - match reset operator discarding the text matched so far
  • \d+ - one or more digits
  • (?=\.0\b) - the lookahead that requires .0 with no word char after to appear immediately to the right of the current position.

Just in case you want to use awk:

url="https://example.com/repository/local/foo-bar/2.15290.0/foo_bar-2.15290.0.tar.gz"
awk 'match($0,/2\.[0-9]+\.0/){print substr($0,RSTART+2,RLENGTH-4)}' <<< "$url"

See the online demo. Finds the 2\.[0-9]+\.0 pattern and prints the part of it from the third char minus the last two.

1st solution: With your shown samples, you could try following awk. Simply set field separators as per your shown samples to /|\\.tar|- and print 2nd last column then.

echo "https://example.com/repository/local/foo-bar/2.15290.0/foo_bar-2.15290.0.tar.gz" | 
awk -F'/|\\.tar|-' '{print  $(NF-3)}'

2nd solution: Using match function in more efficient way, other answer's match may fail since that's running on whole line and in case any more matches found on same line but this will be on the spot with shown samples.

echo "https://example.com/repository/local/foo-bar/2.15290.0/foo_bar-2.15290.0.tar.gz" |
awk -F'/' 'match($NF,/-[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz/){print substr($NF,RSTART+3,RLENGTH-12)}'

3rd solution: Using GNU grep here, which will specifically look for last value of path only and print version as per it.

echo "https://example.com/repository/local/foo-bar/2.15290.0/foo_bar-2.15290.0.tar.gz" |
grep -oP '.*/.*?-\d+\.\K\d+(?=\.\d+)'
Related