Pre-compiled regex with special characters matching - regex

I'm trying to match if a word such as *FOO (* as a normal character) is in a line. My input is a C++ source code. I need to use a pre-compiled regex for this due to program flow requirements, so I tried the following:
$pattern = qr/[^a-zA-Z](\*FOO)[^a-zA-Z]|^\s*(\*FOO)[^a-zA-Z]/;
And I use it like this:
if ($line =~ m/$pattern/) { ... }
It works and catches lines containing *FOO such as hey *FOO.BAR but also matches lines such as:
//FOO programming using stuff and things
which I want to ignore. What am I missing? Is \* not the right way to escape * in a pre-compiled regex in perl? If *FOO is stored in $word and the pattern looks like this:
$pattern = qr/[^a-zA-Z](\\$word)[^a-zA-Z]|^\s*(\\$word)[^a-zA-Z]/;
Is that different from the previous pattern? Because I tried both and the result seems to be the same.
I found a way to bypass this problem by removing the first char of $word and escaping * in the pattern, but if $word = "**.?FOO" for example, how do I create a qr// with $word so that all the meta-characters are escaped?

You do need to escape the *. One way to do it is by the quotemeta \Q operator:
use warnings;
use strict;
my $qr = qr/\Q*FOO/;
while (<DATA>) { print if /$qr/ }
__DATA__
//FOO programming using stuff and things
hey *FOO.BAR
Note that this escapes all ASCII non-"word" characters through the rest of the pattern. If you need to limit its action to only a part of the pattern then stop it using \E. Please see linked docs.
The above determines whether *FOO is in the line, regardless of whether it is a word or a part of one. It is not clear to me which is needed. Once that is specified the pattern can be adjusted.
Note that /\*FOO/ works, too. What you tried failed probably because of all the rest that you are trying to match, which purpose I do not understand. If you only need to detect whether the pattern is present the above does it. if there is a more specific requirement please clarify.
As for the examples: for me that string //FOO... is not matched by the main (first) $pattern you show. The second one won't interpolate $word -- but is firstly much too convoluted. The regex can really tie one in nasty knots when pushed; I suggest to keep it simple as much as possible.

Question 1:
my $word = '*FOO';
my $pattern = qr/\\$word/;
is equivalent to
my $pattern = qr/\\*FOO/; # zero or more '\' followed by 'FOO'
The $word is simply interpolated as is.
To get something equivalent to
my $pattern = qr/\*FOO/;
you should use
my $word = '*FOO';
my $pattern = qr/\Q$word\E/;
By default, an interpolated variable is considered a mini-regular expression, meta characters in the variable such as *, +, ? are still interpreted as meta character. \Q...\E will add a backslash before any character not matching /[A-Za-z_0-9]/, thus any meta characters in the interpolated variable is interpreted as literal ones. Refer to perldoc.
Question 2
I tried
my $pattern = qr/[^a-zA-Z](\*FOO)[^a-zA-Z]|^\s*(\*FOO)[^a-zA-Z]/;
my $line = '//FOO programming using stuff and things';
if($line =~ m/$pattern/){
print "$&\n";
}
else{
print "No match!";
}
and it printed "No match!". I can't explain how you get it matched.

Related

Why is "Search pattern not terminated" in this snippet?

I am trying to update a piece of code to remove any non-alphanumeric character, assign the resultant string to a new variable, and rewrite my HTML to include that value in a new meta tag:
if ( $main::url =~ m/index:Devices/ )
{
my $prodname = getMetaValue(\$doc,'Product_Name');
$prodname =~ tr/[^a-zA-Z0-9 ];
$strippedname =~ $prodname;
$doc =~ s{</head>}{<meta name='Stripped_Name' content='$strippedname' />\n</head>}is;
}
The last line throws a "Search pattern not terminated" error, and I can't figure out why. I use a similar method that does work elsewhere in the script:
if ( $main::url =~ m/index:Devices/ )
{
my $prodname = getMetaValue(\$doc,'Product_Name');
my $brandname = getMetaValue(\$doc,'Manufacturer_Name');
my $devicefullname = $brandname.' '.$prodname;
$doc =~ s{</head>}{<meta name='Device_Full_Name' content='$devicefullname' />\n</head>}is;
}
Any idea why the special character removal script fails me?
Thank you!
The syntax of the tr operator is tr/CHARS/REPLACEMENT/. Further, it performs a transliteration (not regex match) which normally replaces the given literal characters, and in a rather particular way.
But you can do what you want with tr, as it allows ranges and has /c modifier (complement)
$prodname =~ tr/a-zA-Z0-9 //dc;
From Quote-Like-Operators in perlop
If the /c modifier is specified, the SEARCHLIST character set is complemented.
However, using tr/// (specially with /c) is a bit obscure in comparison with using s///, which you also utilize later in the code. Use of s/// would make it clearer
$prodname =~ s/[^a-zA-Z0-9 ]//g;
The modifier /g makes it remove all occurences of characters specified by [^...].
The regex itself can also be written as
s/[^a-z0-9 ]//gi;
but see Negation in perlrecharclass for notes on using /i with negated class and unicode. For an efficiency improvement we can add the + quantifier, s/[...]+//gi, as all occurences need be removed anyway. Note that the tr/// should be much faster here.
With POSIX character classes this can be written as s/[^[:alnum:] ]//g;
tr/// needs three instances of the delimiter, not just one.
$prodname =~ tr/[^a-zA-Z0-9 ];
Moreover, [ means the literal square bracket in tr. Maybe you wanted m// or s///?

Using regex to fetch a value

I have a string:
set a "ODUCTP-1-1-1-2P1"
regexp {.*?\-(.*)} $a match sub
I expect the value of sub to be 1-1-1-2P1
But I'm getting empty string. Can any one tell me how to properly use the regex?
The problem is that the non-greediness of the .*? is leaking over to the .* later on, which is a feature of the RE engine being used (automata-theoretic instead of stack-based).
The simplest fix is to write the regular expression differently.
Because Tcl has unanchored regular expressions (by default) and starts matches as soon as it can, a greedy match from the first - to the end of the string is perfect (with sub being assigned everything after the -). That's a very simple RE: -(.*). To use that, you do this:
regexp -- {-(.*)} $a match sub
Note the --; it's needed here because the regular expression starts with a - symbol and is otherwise confused as weird (and unsupported) option. Apart from that one niggle, it's all entirely straight-forward.
$str = "ODUCTP-1-1-1-2P1";
$str =~ s/^.*?-//;
print $str;
or:
$str =~ /^.*?-(.*)$/;
print $1;

Perl - regex - I want to read and search each line for a string followed by a ";"

I'm playing and learning Perl so that I can read log files. I want to search every line and look for a string of alphanumeric followed by this ; at the beginning of each line.
This is part of what I have:
if ($line =~ /\S([a-zA-Z][a-zA-Z0-9]*)/)
but I think this is wrong.
Please advise.
"Alphanumeric" is a bit ambiguous now, since many people still infected with ASCII think it means A-Z with 0-9, but Perl thinks about it differently depending on the version (Know your character classes under different semantics). As with any regular expression, your job is to design a pattern the includes only what you want and doesn't exclude anything that you do want.
Also, many people still use the ^ to mean the beginning of the string, which is does if there's no /m flag. However, the re module can now set default flags, so your regex might not be what you think it is when another programmer tries to be helpful.
I tend to write things like:
my $alphanum = qr/[a-z0-9]/i;
my $regex = qr/
\A # absolute start of string
(?:$alphanum)+ # I can change this elsewhere
;
/x;
if( $line =~ $regex ) { ... }
Try:
if ($line =~ /^[a-z0-9]+;/i) { ... }
^ matches the start of a line. The + matches once or more. /i makes the search case-insensitive.

Perl Regex (\d*\.\d{2})

I have run into a regex in Perl that seems to be giving me problems. I'm fairly new to Perl - but I don't think that's my problem.
Here is the code:
if ($line =~ m/<amount>(\d*\.\d{2})<\//) { $amount = $1; }
I'm essentially parsing an XML formatted file for a single tag. Here is the specific value that I'm trying to parse.
<amount>23.00000</amount>
Can someone please explain why my regex won't work?
EDIT: I should mention I'm trying to import the amount as a currency value. The trailing 3 decimals are useless.
You shouldn't use regex for parsing HTML, but regardless this will fix it:
if ($line =~ m|<amount>(\d*\.\d{2})\d*<//)| { $amount = $1; }
The \d*\.\d{2} regex fragment only recognize a number with exactly two decimal places. Your sample has five decimal place, and thus does not match this fragment.
You want to use \d*\.\d+ if you need to have a least one decimal place, or \d*\.\d{2,5} if you can have between 2 and 5 decimal place.
And you should not use back-tick characters in your regex as they have no meaning in a regex, and thus are interpreted as regular character.
So you want to use:
if ($line =~ m/<amount>(\d*\.\d{2,5})<\/amount>/) { $amount = $1; }
In a regex pattern, the sequence "{2}" means match exactly two instances of the preceding pattern.
So \d{2} will only match two digits, whereas your input text had five digits at that point.
If you don't want the trailing digits, then you can discard them using \d* outside the capture-parentheses.
Also, if your pattern contains slashes, consider using a different delimiter to avoid having to escape the slashes, e.g.
if ($line =~ m{<amount>(\d*\.\d{2})\d*</}) { $amount = $1; }
Also, if you want to parse XML, then you may want to consider using an XML library such as XML::LibXML.

How to have a variable as regex in Perl

I think this question is repeated, but searching wasn't helpful for me.
my $pattern = "javascript:window.open\('([^']+)'\);";
$mech->content =~ m/($pattern)/;
print $1;
I want to have an external $pattern in the regular expression. How can I do this? The current one returns:
Use of uninitialized value $1 in print at main.pm line 20.
$1 was empty, so the match did not succeed. I'll make up a constant string in my example of which I know that it will match the pattern.
Declare your regular expression with qr, not as a simple string. Also, you're capturing twice, once in $pattern for the open call's parentheses, once in the m operator for the whole thing, therefore you get two results. Instead of $1, $2 etc. I prefer to assign the results to an array.
my $pattern = qr"javascript:window.open\('([^']+)'\);";
my $content = "javascript:window.open('something');";
my #results = $content =~ m/($pattern)/;
# expression return array
# (
# q{javascript:window.open('something');'},
# 'something'
# )
When I compile that string into a regex, like so:
my $pattern = "javascript:window.open\('([^']+)'\);";
my $regex = qr/$pattern/;
I get just what I think I should get, following regex:
(?-xism:javascript:window.open('([^']+)');)/
Notice that it it is looking for a capture group and not an open paren at the end of 'open'. And in that capture group, the first thing it expects is a single quote. So it will match
javascript:window.open'fum';
but not
javascript:window.open('fum');
One thing you have to learn, is that in Perl, "\(" is the same thing as "(" you're just telling Perl that you want a literal '(' in the string. In order to get lasting escapes, you need to double them.
my $pattern = "javascript:window.open\\('([^']+)'\\);";
my $regex = qr/$pattern/;
Actually preserves the literal ( and yields:
(?-xism:javascript:window.open\('([^']+)'\);)
Which is what I think you want.
As for your question, you should always test the results of a match before using it.
if ( $mech->content =~ m/($pattern)/ ) {
print $1;
}
makes much more sense. And if you want to see it regardless, then it's already implicit in that idea that it might not have a value. i.e., you might not have matched anything. In that case it's best to put alternatives
$mech->content =~ m/($pattern)/;
print $1 || 'UNDEF!';
However, I prefer to grab my captures in the same statement, like so:
my ( $open_arg ) = $mech->content =~ m/($pattern)/;
print $open_arg || 'UNDEF!';
The parens around $open_arg puts the match into a "list context" and returns the captures in a list. Here I'm only expecting one value, so that's all I'm providing for.
Finally, one of the root causes of your problems is that you do not need to specify your expression in a string in order for your regex to be "portable". You can get perl to pre-compile your expression. That way, you only care what instructions the characters are to a regex and not whether or not you'll save your escapes until it is compiled into an expression.
A compiled regex will interpolate itself into other regexes properly. Thus, you get a portable expression that interpolates just as well as a string--and specifically correctly handles instructions that could be lost in a string.
my $pattern = qr/javascript:window.open\('([^']+)'\);/;
Is all that you need. Then you can use it, just as you did. Although, putting parens around the whole thing, would return the whole matched expression (and not just what's between the quotes).
You do not need the parentheses in the match pattern. It will match the whole pattern and return that as $1, which I am guess is not matching, but I am only guessing.
$mech->content =~ m/$pattern/;
or
$mech->content =~ m/(?:$pattern)/;
These are the clustering, non-capturing parentheses.
The way you are doing it is correct.
The solutions have been already given, I'd like to point out that the window.open call might have multiple parameters included in "" and grouped by comma like:
javascript:window.open("http://www.javascript-coder.com","mywindow","status=1,toolbar=1");
There might be spaces between the function name and parentheses, so I'd use a slighty different regex for that:
my $pattern = qr{
javascript:window.open\s*
\(
([^)]+)
\)
}x;
print $1 if $text =~ /$pattern/;
Now you have all parameters in $1 and can process them afterwards with split /,/, $stuff and so on.
It reports an uninitialized value because $1 is undefined. $1 is undefined because you have created a nested matching group by wrapping a second set of parentheses around the pattern. It will also be undefined if nothing matches your pattern.