how to avoid lookbehind assertion is not fixed length

Viewed 217

I have a file that contains a version number that I need to output. This version number is apart of a string in this file, that looks something like this:

https://some-link:1234/path/to/file/name-of-file/1.2.345/name-of-file_CXP123456-1.2.345.jar"

I need to get the version number, which is 1.2.345.

This grep command works: grep -Po '(?<=/name-of-file_CXP123456-/)\d.\d.\d\d\d'. However, the CXP number changes and as such I thought I could do something like this: grep -Po '(?<=/name-of-file_*-/)\d.\d.\d\d\d' but that gives the following:

grep: lookbehind assertion is not fixed length

Is there anything I can add to the grep statement to avoid this?

Ultimately, this is part of a stage in Jenkins to get this version number. The sh command looks something like this:

VERSION = sh 'ssh -tt user@ip-address "cat dir/file*.content | grep -Po '(?<=/name-of-file_*-/)\d.\d.\d\d\d' 1>&2"'
4 Answers

You can use

grep -Po '/name-of-file_.*-\K\d+(?:\.\d+)+'

See the regex demo. Details:

  • /name-of-file_ - a literal text
  • .* - any zero or more chars other than line break chars as many as possible
  • - - a hyphen
  • \K - a match reset operator that omits all text matched so far from the memory buffer
  • \d+ - one or more digits
  • (?:\.\d+)+ - one or more sequences of a . and one or more digits.

You don't need lookbehind for this job. You also don't need PCREs, or grep at all.

#!/usr/bin/env bash
#              ^^^^- bash, *not* sh
case $BASH_VERSION in '') echo "ERROR: bash required" >&2; exit 1;; esac

string="https://some-link:1234/path/to/file/name-of-file/1.2.345/name-of-file_CXP123456-1.2.345.jar"
regex='.*/name-of-file_CXP[[:digit:]]+-([[:digit:].]+)[.]jar'

if [[ $string =~ $regex ]]; then
  echo "Version is ${BASH_REMATCH[1]}"
else
  echo "No version found in $string"
fi

Maybe too long for a comment... It looks like the version number is the 2nd-to last field if you split on forward slash?

  • rev | cut -d/ -f 2 | rev
  • awk -F/ '{print $(NF-1)}'
  • perl -lanF/ -e 'print $F[-2]'

Or even something like: basename $(dirname $(cat filename))

For those that are really desperate there is another solution which requires you to pre-build your regex string. It's not a solution I would recommend but if there is really no other way no one can stop you.

While even with this you won't have true dynamic look-behinds and it is still quite limited it is an option available to you.

The idea is to build the look-behind for each possible length you need it to be. So for example only match if it's not preceded by a # (0 to a 100 characters look-behind).

reg='';
for ((i = 0 ; i <= 100 ; i++)); do reg+='(?<!#.{'"${i}"'})'; done;
reg+='someVariableName=.*?($|;|\\n)';
grep --perl-regexp "$reg" /usr/local/mgmsbox/msc/scripts/msc.cfg

This might not be the best example but it gets the idea across. This solution has it's own pitfalls. For example you need to double escape \\ escape-sequences like \n and any character that should not be interpreted should be put in a single-quote string (or use printf).

Related