How can I extract groups of non-whitespace from a line in Perl?
I'm writing a program that has to get values from a file. In the file each line indicates an entity. Each entity has 开发者_高级运维three values. For example:
Value1 Value2 value3
I have a regular expresion to match them
m/(.*?) (.*?) (.*?)/m;
But it seems that the third value in never matched! The only way to match the third value is to add another value in the file and another "matching brackets" in the expresion. But this does not satisfy me.
before you think of doing things with regex, think of whether it can be solved simply without it. if you want to get the entities, a much simpler approach is to split them up. the elements of the returned list will be what you want.
@s = split /\s+/ , $line;
What's going on
Let's simplify the capturing out of your regex for a while, as it isn't responsible for whats happening. Your regex is thus as such:
/.*? .*? .*?/
The meaning of .*?
is "match any character (except a newline), none to many times, as few as possible."
In this context, the first .*?
would attempt to match zero characters from the string, then fail on the next regex element, the space. It would try again matching one, two... characters from the string, and will first succeed when the next character is an actual space.
Put in another way, it's the fact we've got a space after the .*?
group that makes it match what you want. Else it would just happily stop matching at zero characters.
This is precisely what's going on for your third match. Since your regex ends there, a null match does satisfy the regex group, and is the preferred match.
Ways to avoid it
As the other answers have put it, possible solutions include:
split
(best transcription of intended semantice IMO)- making the last capture greedy (
.*
instead of.*?
) - adding something (anything that matches) past the last capture.
$
if the line ends there - matching on non-spaces (
\S
) instead of any character (.
). This would work with either greedy (\S*
) or nongreedy (\S*?
) matches.
Put a $
at the end of the regex
to solve this issue:
m/(.*?) (.*?) (.*?)$/m;
Alternatively you can make the last part greedy
:
m/(.*?) (.*?) (.*)/m;
You don't really want to use the *
quantifier in this case, and you don't want to let those quantifiers be greedy. The trick in a regular expression is to describe the pattern specifically as you can.
The line you want to match has:
- Some non-whitespace
- Some whitespace
- repeat two more times
Once you've described the situation, you can translate it into a regex. You might start with a literal translation of your description:
my @values = /(\S+) (\S+) (\S+)/;
Since you used \S
, the parts of the pattern in the capture can't get past the whitespace to match more than you intend, as a .*
can.
You've repeated parts of the pattern, so you can condense that. Since you are just capturing groups of whitespace, make that a global match instead:
my @values = /(\S+)/g;
You can also think about the converse. Rather than capturing the non-whitespace, you can throw away the whitespace with split:
my @values = split /\s+/;
精彩评论