I've been working on something the last week that brought me back into sometimes frightening world of regular expressions. But as the saying goes, we only fear what we don't understand. So a little knowledge can be a wonderful thing. In this particular situation I was looking for a regular expression to determine if a number fell within a certain range. I know I can use an operator statement in a compound expression like ($x -gt 0) -AND ($x -le 10) but I that would require additional parsing. Plus I decided this was an opportunity to whittle away at my regular expression deficiencies. Now I know how to tell using regular expressions if a string contains a number between say 1 and 10. Here's how.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
The first step is to remember that regular expressions are all about patterns. We're not looking at values. When constructing a number pattern you have to break it down. Let's start simply and see if the number in my text is between 0 and 9. My regular expression uses a range: [0-9].
[cc lang="PowerShell"]
PS S:\> $x=4
PS S:\> $x -match "[0-9]"
True
PS S:\> $x=3
PS S:\> $x -match "[0-9]"
True
PS S:\> $x=-1
PS S:\> $x -match "[0-9]"
True
PS S:\> $x=11
PS S:\> $x -match "[0-9]"
True
[/cc]
We're starting simple. As you can see I get the results I expected until I hit numbers like -1 and 11. Those still return True but that's not what I want. That's because regular expression patterns "float". Take 11. My expression says look for anything that is between 0 and 9. Looking at $matches from the last comparison I can see I in fact matched on 1.
[cc lang="PowerShell"]
PS S:\> $matches
Name Value
---- -----
0 1
[/cc]
The solution is to anchor my pattern.
[cc lang="PowerShell"]
PS S:\> $x -match "^[0-9]$"
False
[/cc]
With $x still 11 now I get the result I expected because my pattern says look for something that starts (^) and ends ($) with a number between 0 and 9.
[cc lang="PowerShell"]
PS S:\> $x=6
PS S:\> $x -match "^[0-9]$"
True
[/cc]
A 0-9 range is rarely sufficient. More than likely I need a greater range, say up to 25. This is where it gets interesting. You need to create a pattern for each digit. We'll focus on validating 20-25. I need a pattern for the first part, a 2 and something for second part to cover 0-5. I'll end up with a more complex regular expression: "^[2][0-5]$". Here's a quick way to test.
[cc lang="PowerShell"]
PS S:\> (19..30) -match "^[2][0-5]$"
20
21
22
23
24
25
[/cc]
I can see that only numbers 20-25 are returned. To validate numbers 1 to 25 I need to combine my expressions.
[cc lang="PowerShell"]
PS S:\> (1..30) -match "^([1-9]|[1][0-9]|[2][0-5])$" | measure-object
Count : 25
Average :
Sum :
Maximum :
Minimum :
Property :
[/cc]
Rather than list out all the matching numbers which you can so, I simply measured the result so you could see I in fact got 25 matches. My expression is slightly more complicated. The first part [1-9] matches 1-9. The second part, [1][0-9], after the OR (|) gets all numbers that start with 1 and end with 0-9. In other words, 10-19. The last part, [2][0-5], covers numbers 20-25. Do you see it? Ready for one more? Here's an expression that validates 1-50.
[cc lang="PowerShell"]
PS S:\> (1..100) -match "^([1-9]|[1-4][0-9]|[5][0])$" | measure-object
Count : 50
Average :
Sum :
Maximum :
Minimum :
Property :
[/cc]
The second part matches all numbers that start with 1-4 and end with 0-9 which covers 10-49. The last part finds numbers that start with 5 and end in 0. Taking this a step further I can now beginning writing PowerShell expressions like these:
[cc lang="PowerShell"]
PS S:\> "File45" -match "^file([1-9]|[1-4][0-9]|[5][0])$"
True
PS S:\> "File95" -match "^file([1-9]|[1-4][0-9]|[5][0])$"
False
PS S:\> "File5" -match "^file([1-9]|[1-4][0-9]|[5][0])$"
True
PS S:\> "File15" -match "^file([1-9]|[1-4][0-9]|[5][0])$"
True
[/cc]
To write a pattern for a 3 digit number take what I've shown you here and simply extend it for the third digit. I'll leave that exercise to you.
This gave me an idea to try a slight tweak that’s a little more obscure, but more easily scales to large numbers:
1..100 -match “^($([string]::join(‘|’,1..50)))$” | measure-object
Count : 50
Average :
Sum :
Maximum :
Minimum :
Property :
Cheers,
Chris