Writing a regex pattern for text if not 255/255 - regex

I'm currently writing a Perl program where I need a regex pattern, but there is something I don't know how to do.
Text
Reliability: 255/255
I need to write a regex where if reliability is 255/255 then it's okay (I have that one: Reliability:\s+(255\/255))
but if Reliability is different from 255/255 it's not.
I need to do it without an else. I just need the regex if possible.
Clarification
I send a command on a router : show interface $WAN_Int where $WAN_Int is an IP address. I put the result in a table. Then I read the table and I search for Reliability: 255/255. if it's 255, it's OK. If it's not, there is a problem.

my $ok = $string eq 'Reliability: 255/255';
or, if it must be a regex
my $ok = $string =~ m{^Reliability: 255/255$};
The $ok is 1 if $string is equal to the text or an empty string otherwise.
Both eq operator
and the regex's match operator in scalar context
return "true" (1) on success or false (an empty string) otherwise. So you can use them in an expression and assign their return (or decide based on it).
Please keep in mind the precedence table when doing this or use parenthesis liberally.
Given the clarification a regex is better here, to allow for flexibility in input as it comes from another interface which may have (or pick up) slight variations in the expected format.
Then allow for multiple spaces between words (\s+), for the phrase being only a part of the string (drop anchors ^ and $), perhaps for the word not being capitalized
my $ok = $string =~ m{\b[Rr]eliability:\s+255/255\b};
where \b is the word-boundary anchor, since trailing numbers after 255 may compromise this.

This may work for you:
Reliability:\s+(?!255)\d+/255\s*$
https://regex101.com/r/1JAsG6/5/
Also, for matching 255 on your original regexp, It can be simplified (you don't need classes and you can avoid the group)
Reliability:\s+255/255\s*$
https://regex101.com/r/LnfXVI/3/
NOTE: If you use / as a delimiter for the regexp, you may need to scape / on the regxes as \/
Extra: If you would want to also disallow strings like Reliability: 123456/255, use this instead: Reliability:\s+(?!255|0\d)\d{1,3}/255\s*$
https://regex101.com/r/1JAsG6/8/

Related

How to use Regular expression in perl if both regular expression and strings are variables

I have two variables coming from some user inputs. One is a string that needs to be checked and other one is a regular expression as below.
Following code doesn't work.
my $pattern = "/^current.*$/";
my $name = "currentStateVector";
if($name =~ $pattern) {
print "matches \n";
} else {
print "doesn't match \n";
}
And following does.
if($name =~ /^current.*$/) {
print "matches \n";
} else {
print "doesn't match \n";
}
What's the reason for this. I've the regular expression stored in a variable. Is there another way to store this variable or modify it?
The double-quotes that you use interpolate -- they first evaluate what's inside them (variables, escapes, etc) and return a string built with evaluations' results and remaining literals. See Gory details of parsing quoting constructs for an illuminating discussion, with lots of detail.
And your example string happens to have a $/ there, which is one of Perl's global variables (see perlvar) so $pattern is different than expected; print it to see. (In this case the / is erroneous as discussed below but the point stands.)
Instead, either use single quotes to avoid interpretation of characters like $ and \ (etc) so that they are used in regex as such
my $pattern = q(^current.*$);
or, better, use the regex-specific qr operator
my $pattern = qr/^current.*$/;
which builds from its string a proper regex pattern (a special type of Perl value), and allows use of modifiers. In this case you need to escape characters that have a special meaning in regex if you want them to be treated as literals.
Note that there's no need for // for the regex, and they wouldn't be a part of the pattern anyway -- having them around the actual pattern is wrong.
Also, carefully consider all circumstances under which user input may end up being used.
It is brought up in a comment that users may submit a "pattern" with extra /'s. That'd be wrong, as mentioned above; only the pattern itself should be given (surrounded on the command-line by ', so that the shell doesn't interpret particular characters in it). More detail follows.
The /'s are clearly not meant as a part of the pattern, but are rather intended to come with the match operator, to delimit (quote) the regex pattern itself (in the larger expression) so that one can use string literals in the pattern. Or they are used for clarity, and/or to be able to specify global modifiers (even though those can be specified inside patterns as well).
But then if users still type them around the pattern the regex will use those characters as a part of the pattern and will try to match a leading /, etc; it will fail, quietly. Make sure that users know that they need to give a pattern alone, with no delimiters.
If this is likely to be a problem I'd check for delimiters and if found carry on with a "loud" (clear) warning. What makes this tricky is the fact that a pattern starting and ending with a slash is legitimate -- it is possible, if somewhat unlikely, that a user may want actual /'s in their pattern. So you can only ask, or raise a warning, not abort.
Note that with a pattern given in a variable, or with an expression yielding a pattern at runtime, the explicit match operator and delimiters aren't needed for matching; the variable or the expression's return is taken as a search pattern and used for matching. See The basics (perlre) and Binding Operators (perlop).
So you can do simply $name =~ $pattern. Of course $name =~ /$pattern/ is fine as well, where you can then give global modifiers after the closing /
The slashes are part of the matching operator m//, not part of the regex.
When I populate the regex from user input
my $pattern = shift;
and run the script as
58663971.pl '^current.*$'
it matches.

Lookaround assertions in Perl

im confused what is the use of these lookaround assertions in perl?
example this one:
(?=pattern)
or the positive lookahead. So here's my questions:
How are these useful? what sort of instances they are used?
And related to question 1, why would i want to look ahead of the regex pattern? isnt it more work? looking ahead and then executing the pattern matching again.
I need a very clear example if possible. Thanks
To uppercase what's in between commas, you could use:
(my $x = 'a,b,c,d,e') =~ s/(?<=,)([^,]*)(?=,)/ uc($1) /eg; # a,B,C,D,e
a,b,c,d,e
Pass 1 matches -
Pass 2 matches -
Pass 3 matches -
If you didn't use lookarounds, this is what you'd get,
(my $x = 'a,b,c,d,e') =~ s/,([^,]*),/ ','.uc($1).',' /eg; # a,B,c,D,e
a,b,c,d,e
Pass 1 matches ---
Pass 2 matches ---
Not only does the lookahead avoid repetition, it doesn't work without it!
Another somewhat common use is as part of a string equivalent to [^CHAR].
foo(?:(?!foo|bar).)*bar # foo..bar, with no nested foo or bar
You can use it to narrow down character classes.
\w(?<!\d) # A word char that's not a digit.
Although this can now be done using (?[ ... ]).
It's also useful in more esoteric patterns.
/a/ && /b/ && /c/
can be written as
/^(?=.*?a)(?=.*?b).*?c/s
lookahead lets you check for a pattern without actually matching it.
When you do a(?=b) ,you would match a if its followed by b. Note:it doesn't match b.
So,
1>You can extract hello(without #) from #hello# using
(?<=#)hello(?=#)
2>You can validate passwords with requirements such as a password must have 2 digits,2 letters or more with any other character
^(?=(.*\d){2})(?=(.*[a-z]){2}).*$
Try doing above without lookahead ,you would realize it's importance
I have found lookaheads especially useful for checking multiple conditions. For example, consider a regex that checks that a password has at least one lowercase, one uppercase, one numeric, and one symbol character, and is at least 8 characters in length:
^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9]).{8,}$
Try to devise a regex to do the same thing without lookahead assertions! It's possible, but it's extremely cumbersome.
Meanwhile, I've found lookbehinds especially useful for checking boundary conditions—that is, for example, matching a string of 0's, unless it's preceded by another number, like 1000067.
These are my experiences but certainly there are many more practical uses and the way everyone uses a tool can vary from person to person.
There are many reasons to use lookarounds, e.g.
limiting the substring that is considered to be matched: s/(?<=[0-9])+(?=[0-9])/-/ instead of s/([0-9])+([0-9])/$1-$2/.
and-ing various conditions together: /(?=\p{Uppercase}\p{Lowercase})\p{InBasicLatin}{2,}/.
Lookaround assertions is useful when you need a pattern to help locate the match but you don't want the pattern to be part of what is captured.
Here's a simple scenario with lookahead assertion:
Let's say I have
my $text = '98 degrees, 99 Red Balloons, 101 Dalmatians'
and I want to change the number of red balloons from its previous value to 9001, so I use
$text =~ s/\d+(?=Red Balloons)/9001/;

Regex to match anything but more than two spaces

I'm trying to create two a regex to add quotes to some values in a string. Basically the string would be like this:
999 date Doe, John E. London 123456789
And I want to surround the name so that if this file is exported to a csv, it won't be separated. This is what I have so far
$line =~ s/([^\s{2,}]*,[^\s{2,}]*)/"$1"/g;
I think it should find any comma and anything near it until it finds two or more spaces but it's not working. Thanks for the help.
You asked for anything except 2 or more spaces.
I agree that unpack is the more natural way to do this. But split is a way to use a cookie-cutter in the shape of a pattern. Anything not in that pattern is a return field. So this:
#fields = split /\h{2,}/, $line;
$line = join(" " x 2 => map { "($_)" } #fields);
might be enough.
If this is a fixed-width data (and my guess, it is), better use unpack (or plain old substr,etc..) rather than regular expressions.
[] contain a range of characters that are allowed at that possible, 2-space isn't a character.
Maybe:
$line =~ s/ (.*? .*?) / "\1" /g;
You'll probably need to be more explicit about the format to avoid matches against ' '.
$line =~ s/ (\w+?, [\w ]+?.) / "\1" /g;
To avoid repeating the space in the replacement, look-around assertions could be used, which could also fix the issue of items at the beginning and end of the line:
$line =~ s/(?<=^| )(\w+?, [\w ]+?.)(?=$| )/"\1"/g;
Also be careful of your original format - are you sure it isn't just column aligned? (In which case a sufficiently long name or date might not allow 2+ spaces between columns).
Try this:
s/.* \K(.*),(.*?) /"$1,$2" /
Logically, this means: Find a substring between two spaces and a comma, where the two spaces are as far right as possible, and then a substring between that comma and two spaces, where the substring is as short as possible.
Your approach can work too, if you get the syntax for negative lookaheads right.
The sample text you supplied seems to be separated either by tabs or spaces (column aligned?). It is important to know which, or the regexp will not work. It is also important to know whether the pattern is consistent throughout the file.
If it is aligned by columns, the easiest and probably safest way is to simply count off characters. E.g.:
s/(^.{20})(\S*) /$1"$2"/;
(You will have to adjust the number 20 yourself. I just approximated.)
Note that I am chopping off two spaces at the end of the name field in a reckless manner. This is to not screw up the format for the following values. If however the field is filled to the brim, there might not be two spaces at the end, and the regexp will miss. But then, on the other hand, you would not be able to fit quotes there anyway.
When dealing with these types of files, I do not think it is safe to use generic searches. If you are counting on commas to only appear in the names, sooner or later you will find someone who thought that "Bronx, New York" should be in the city field, and your regexp will be screwed.
A somewhat more strict, but complicated regexp would include the previous fields:
$date='\d{2}-\d{2}-\d{2}'; # this might work for dates such as 11-10-23
s/^(\d+\s+$date\s+)(\S+) /$1"$2"/;
Same thing here, if the name field is not big enough to fit two quotes, it won't be added. You should check your file and see if that is ever the case. If it is the case, you will need to deal with it somehow.
I sometimes find that putting certain field's regexps in separate variables helps with legibility, such as with $date above.
Good luck!

Regular Expression, dynamic number

The regular expression which I have provided will select the string 72719.
Regular expression:
(?<=bdfg34f;\d{4};)\d{0,9}
Text sample:
vfhnsirf;5234;72159;2;668912;28032009;4;
bdfg34f;8467;72719;7;6637912;05072009;7;
b5g342sirf;234;72119;4;774582;20102009;3;
How can I rewrite the expression to select that string even when the number 8467; is changed to 84677; or 846777; ? Is it possible?
First, when asking a regex question, you should always specify which language you are using.
Assuming that the language you are using does not support variable length lookbehind (and most don't), here is a solution which will work. Your original expression uses a fixed-length lookbehind to match the pattern preceding the value you want. But now this preceding text may be of variable length so you can't use a look behind. This is no problem. Simply match the preceding text normally and capture the portion that you want to keep in a capture group. Here is a tested PHP code snippet which grabs all values from a string, capturing each value into capture group $1:
$re = '/^bdfg34f;\d{4,};(\d{0,9})/m';
if (preg_match_all($re, $text, $matches)) {
$values = $matches[1];
}
The changes are:
Removed the lookbehind group.
Added a start of line anchor and set multi-line mode.
Changed the \d{4} "exactly four" to \d{4,} "four or more".
Added a capture group for the desired value.
Here's how I usually describe "fields" in a regex:
[^;]+;[^;]+;([^;]+);
This means "stuff that isn't semi-colon, followed by a semicolon", which describes each field. Do that twice. Then the third time, select it.
You may have to tweak the syntax for whatever language you are doing this regex in.
Also, if this is just a data file on disk and you are using GNU tools, there's a much easier way to do this:
cat file | cut -d";" -f 3
to match the first number with a minimum of 4 digits
(?<=bdfg34f;\d{4,};)\d{0,9}
and to match the first number with 1 or more length
(?<=bdfg34f;\d+;)\d{0,9}
or to match the first number only if the length is between 4 and 6
(?<=bdfg34f;\d{4,6};)\d{0,9}
This is a simple text parsing problem that probably doesn't mandate the use of regular expressions.
You could take the input line by line and split on ';', i.e. (in php, I have no idea what you're doing)
foreach (explode("\n", $string) as $line) {
$bits = explode(";", $line);
echo $bits[3]; // third column
}
If this is indeed in a file and you happen to be using PHP, using fgetcsv would be much better though.
Anyway, context is missing, but the bottom line is I don't think you should be using regular expressions for this.

Newbie regex question - detect spam

Here's my regex newbie questions:
How can I check if a string has 3 spam words? (for example: viagra, pills and shop)
How can I detect also variations of those spam words like "v-iagra" or "v.iagra" ? (one additional character)
Regex doesn't seem like quite the right hammer for this particular nail. For your list, you can simply throw all of you blacklisted words in a sorted list of some kind, and scan each token against that list. Direct string operations are always faster than invoking the regular expression engine du jour.
For your variations ("v-iagra", et. al) I'd remove all non-characters (as #Kinopiko suggested) and then run them past your blacklist again. If you're wary of things like "viiagra", et cetera, I'd check out Aspell. It's a great library, and looks like CPAN has a Perl binding.
How can I check if a string has 3 spam words? (for example: viagra,pills and shop)
A regex to spot any one of those three words might look like this (Perl):
if ($string =~ /(viagra|pills|shop)/) {
# spam
}
If you want to spot all three, a regex alone isn't really enough:
my $bad_words = 0;
while ($string =~ /(viagra|pills|shop)/g) {
$bad_words++;
}
if ($bad_words >= 3) {
# spam
}
How can I detect also variations of those spam words like "v-iagra" or "v.iagra" ? (one additional character)
It's not so easy to do that with just a regex. You could try something like
$string =~ s/\W//g;
to remove all non-word characters like . and -, and then check the string using the test above. This would strip spaces too though.