user46581
user46581

Reputation: 200

Use regex to find shortest possible match from multiline string

I have a multi-line string with given representation:

text1 (arbitrary chars and lines)\n
<hr>\n
Bitmap: ./media/logo.bmp\n
text2 (arbitrary chars and lines)\n
text3 (arbitrary chars and lines)\n
<hr>\n
Bitmap: ./media/logo.bmp\n
text2 (arbitrary chars lines)\n
\n

I want to match this substring that always occures twice in the string (once always in the end):

<hr>\n
Bitmap: ./media/logo.bmp\n
text2 (arbitrary chars and lines)\n

When I try to match with re.search, it returns the long match:

regex = re.compile('<hr>\n'
                   'Bitmap: [\S\n ]*'
                   '$')
print(re.search(regex, string).group())

>> '<hr>\nBitmap: ./media/logo.bmp\ntext2 (arbitrary chars and lines)\ntext3 (arbitrary chars and lines)\n<hr>\nBitmap: ./media/logo.bmp\ntext2 (arbitrary chars and lines)\n\n'

Is it possible to use regex to find the short match?

Solution:
Lookahead with OR operator returns two matches (one longer and one shorter):

regex = re.compile('<hr>\n'
                   'Bitmap: [\S]*\n'
                   '[\s\S]*?(?=<hr>|\n\Z)')
print(re.findall(regex, string))
>> ['<hr>\nBitmap: ./media/logo.bmp\ntext2 (arbitrary chars and lines)\ntext3 (arbitrary chars and lines)\n', '<hr>\nBitmap: ./media/logo.bmp\ntext2 (arbitrary chars lines)\n']

Upvotes: 0

Views: 356

Answers (2)

Ryszard Czech
Ryszard Czech

Reputation: 18611

Use

(?m)^<hr>\r?\nBitmap:[\s\S]*?(?=^<hr>$|\Z)

See proof.

Explanation

--------------------------------------------------------------------------------
  (?m)                     set flags for this block (with ^ and $
                           matching start and end of line) (case-
                           sensitive) (with . not matching \n)
                           (matching whitespace and # normally)
--------------------------------------------------------------------------------
  ^                        the beginning of a "line"
--------------------------------------------------------------------------------
  <hr>                     '<hr>'
--------------------------------------------------------------------------------
  \r?                      '\r' (carriage return) (optional (matching
                           the most amount possible))
--------------------------------------------------------------------------------
  \n                       '\n' (newline)
--------------------------------------------------------------------------------
  Bitmap:                  'Bitmap:'
--------------------------------------------------------------------------------
  [\s\S]*?                 any character of: whitespace (\n, \r, \t,
                           \f, and " "), non-whitespace (all but \n,
                           \r, \t, \f, and " ") (0 or more times
                           (matching the least amount possible))
--------------------------------------------------------------------------------
  (?=                      look ahead to see if there is:
--------------------------------------------------------------------------------
    ^                        the beginning of a "line"
--------------------------------------------------------------------------------
    <hr>                     '<hr>'
--------------------------------------------------------------------------------
    $                        before an optional \n, and the end of a
                             "line"
--------------------------------------------------------------------------------
   |                        OR
--------------------------------------------------------------------------------
    \Z                       the end of the string
--------------------------------------------------------------------------------
  )                        end of look-ahead

Upvotes: 2

Christian Baumann
Christian Baumann

Reputation: 3434

This works: <hr>\nBitmap:.*\n(?:.*\n){1,2}

See: https://regex101.com/r/i64K0W/3

The problem in your regex was the *, which is greedy.

Upvotes: 0

Related Questions