Monday, February 26, 2018

Testing for multiple strings without much clutter using powershell

Yes, this hopefully will be quick, and yes it is powershell, which does not make me feel as dirty as if it was Windows. So hear me out.

I wrote a script a while ago that I needed to look for a pattern inside a string and then do something. In its simplest form, the code could look like this (second line is there to show the entire string):

$the_string = "There are pickles in a jar"
$the_string

if ($the_string -match "pickles")
{
        "Found me pickles"
}
else
{
        "nothing to declare"
}

If you wonder why I am using -match instead of -contains, there is a nice discussion you want to check. At least I learned a lot from it. When we run the script, which is henceforth called switchtest.ps1, we get

PS C:\Users\dalek> powershell -file .\dev\switchtest.ps1
There are pickles in a jar
Found me pickles
PS C:\Users\dalek>

So far so good. But, what if we want to do things based on whether other substrings are in the string? After all, instead of one single string we might be looping over a list of them, or reading a file and doing things based on what we find on a line-by-line-basis. We could add more if statements but that gets nasty quickly:

$the_string = "There are pickles in a jar"
$the_string

if ($the_string -match "pickles")
{
        "Found me pickles"
}

elseif ($the_string -match "there")
{
        "There is here"
}
else
{
        "nothing to declare"
}

Notes:

  • By default powershell is case insensitive; this is consistent with how DOS and Windows behaves, which is exactly the opposite of UNIX in general and Linux specifically.
  • If you have used other programming languages like python or C, you might remember switch statements (switch, then case-this and case-that). And, they are also implemented in powershell without the case word being explicitly used but the behavior is there for all to see.
  • -match looks for the substring anywhere in the string. So, it would find there in:
    • $the_string = "There are pickles in a jar"
    • $the_string = "Are there pickles in a jar?"
    • $the_string = "Pickles are in a jar over there"
Let's now redo switchtest.ps1 using case:
$the_string = "There are pickles in a jar"
$the_string

switch -wildcard ($the_string)
{
        "*pickles*" {"Found me pickles"}
        "there*" {"There is here"}
        default {"nothing to declare"}
}

and then run it

PS C:\Users\dalek> powershell -file .\dev\switchtest.ps1
There are pickles in a jar
Found me pickles
There is here
PS C:\Users\dalek>

The -wildcard option is where the main magic is. It allows me to enter a string as the search pattern, such as "pickles". The more astute of you probably noticed the asterisk (*) surrounding the search string. Without the first, it would only look for a string that starts with "pickles"; Since we only want to look for there when it begins the string we do not need the leading *. There is a thread in the Spiceworks forum showing another example of looking for strings that begin with a, in their case, specific letter using a case statement. The second one is so it will not look only for string ending with that substring.

There is nothing forcing each case statement to be a one-liner. In fact, it would be clearer to write the second one as

// Do something if the string begins with "there" 
        "there*" 
             {
                   "There is here"
             }

specially if we are going to do more than just write out a string.

Another thing you might also have noticed is the behaviour of the output does not match the one we did using the if statement. Reason is unless we specifically tell the switch statement to stop testing once it finds a match, it will keep going down to the list (the default is only done if nothing matches. Sometimes that is exactly what you want to do, but let's assume that is not the case. Just as in other languages, the command we need to give it is break. Let's add them

$the_string = "There are pickles in a jar"
$the_string

switch -wildcard ($the_string)
{
        "*pickles*" {"Found me pickles"; break}
        "there*" {"There is here"; break}
        default {"nothing to declare"}
}

and try it once more

PS C:\Users\dalek> powershell -file .\dev\switchtest.ps1
There are pickles in a jar
Found me pickles 
PS C:\Users\dalek>

Now the output looks just like the original script, but it is much cleaner and easier to expand.