With
preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE);
is it possible to search a string in reverse? I.e., return the position of the last occurrence of the pattern in the subject similar to strripos
.
Or do I have to return the position of all matches with preg_match_all
and use the last element of $matches
?
Return Values ¶ preg_match() returns 1 if the pattern matches given subject , 0 if it does not, or false on failure. This function may return Boolean false , but may also return a non-Boolean value which evaluates to false .
preg_match stops looking after the first match. preg_match_all , on the other hand, continues to look until it finishes processing the entire string. Once match is found, it uses the remainder of the string to try and apply another match.
PHP preg_match() function. The preg_match() function is a built-in function of PHP that performs a regular expression match. This function searches the string for pattern, and returns true if the pattern exists otherwise returns false. Generally, the searching starts from the beginning of $subject string parameter.
In PHP, you can use the preg_match() function to test whether a regular expression matches a specific string. Note that this function stops after the first match, so this is best suited for testing a regular expression more than extracting data.
"Greedy" is the keyword here.
*
is by default greedy, and *?
limits greediness to the bare minimum.
So the solution is to use the combination, e.g. (searching for last period followed by a whitespace),
/^.*\.\s(.*?)$/s
^
is the beginning of text.*
eats as much as it can, including matching patterns\\.\s
is the period followed by a whitespace (what I am looking for)(.*?)
eats as little as possible. Capture group () so I could address it as a match group.$
end of texts
- makes sure newlines are ignored (not treated as $
and ^
- .
(dot) matches newline)PHP doesn't have a regex method that search a string from right to left (like in .NET). There are several possible recipes to solve that (this list isn't exhaustive, but it may provide ideas for your own workaround):
preg_match_all
with PREG_SET_ORDER
flag and end($matches)
will give you the last match setstrrev
and building a "reversed" pattern to be used with preg_matchpreg_match
and building a pattern anchored at the end of the string that ensures there is no more occurrences of the searched mask until the end of the string\K
to start the match result at the position you want. Once the end of the string is reached, the regex engine will backtrack until it finds a match.Examples with the string $str = 'xxABC1xxxABC2xx'
for the pattern /x[A-Z]+\d/
Way 1: find all matches and displays the last.
if ( preg_match_all('/x[A-Z]+\d/', $str, $matches, PREG_SET_ORDER) )
print_r(end($matches)[0]);
Demo
Way 2: find the first match of the reversed string with a reversed pattern, and displays the reversed result.
if ( preg_match('/\d[A-Z]+x/', strrev($str), $match) )
print_r(strrev($match[0]));
Demo
Note that it isn't always so easy to reverse a pattern.
Way 3: Jumps from x to x and checks with the negative lookahead if there's no other x[A-Z]+\d
matches from the end of the string.
if ( preg_match('/x[A-Z]+\d(?!.*x[A-Z]+\d)/', $str, $match) )
print_r($match[0]);
Demo
Variants:
With a lazy quantifier
if ( preg_match('/x[A-Z]+\d(?!.*?x[A-Z]+\d)/', $str, $match) )
print_r($match[0]);
or with a "tempered quantifier"
if ( preg_match('/x[A-Z]+\d(?=(?:(?!x[A-Z]+\d).)*$)/', $str, $match) )
print_r($match[0]);
It can be interesting to choose between these variants when you know in advance where a match has the most probabilities to occur.
Way 4: goes to the end of the string and backtracks until it finds a x[A-Z]+\d
match. The \K
removes the start of the string from the match result.
if ( preg_match('/^.*\Kx[A-Z]+\d/', $str, $match) )
print_r($match[0]);
Way 4 (a more hand-driven variant): to limit backtracking steps, you can greedily advance from the start of the string, atomic group by atomic group, and backtrack in the same way by atomic groups, instead of by characters.
if ( preg_match('/^(?>[^x]*\Kx)+[A-Z]+\d/', $str, $match) )
print_r($match[0]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With