What's the PowerShell syntax for multiple values in a switch statement?
Asked Answered
W

10

101

I basically want to do this:

switch($someString.ToLower())
{
    "y", "yes" { "You entered Yes." }
    default { "You entered No." }
}
Winger answered 16/8, 2010 at 13:47 Comment(0)
E
114
switch($someString.ToLower()) 
{ 
    {($_ -eq "y") -or ($_ -eq "yes")} { "You entered Yes." } 
    default { "You entered No." } 
}
Enloe answered 16/8, 2010 at 13:57 Comment(5)
can also do {$_ -in "y","yes"} but I'm not sure since which PS version.Immerge
@wannabeprogrammer The In operator was introduced in Windows PowerShell 3.0.Chasidychasing
-eq is case insensitive when performing string comparison so ToLower is not neededProteose
Likewise, you can do { "y", "yes" -contains $_ } And this is also case-insensitive, so 'YES' and 'yeS' and so on also work.Alfonzoalford
The switch is also case-insensitive by default.Lashay
E
65

I found that this works and seems more readable:

switch($someString)
{
    { @("y", "yes") -contains $_ } { "You entered Yes." }
    default { "You entered No." }
}

The "-contains" operator performs a non-case sensitive search, so you don't need to use "ToLower()". If you do want it to be case sensitive, you can use "-ccontains" instead.

Epideictic answered 6/4, 2012 at 13:59 Comment(1)
"-contains" does match case-insensitive, however it also will find the search string "yes" anywhere within $_. I generally stay away from -contains unless I need that kind of comparison, as it would potentially return TRUE if $someString contains the substring. Substring matching also requires more clock cycles.Ange
G
50

You should be able to use a wildcard for your values:

switch -wildcard ($someString.ToLower())
{
    "y*" { "You entered Yes." }
    default { "You entered No." }
}

Regular expressions are also allowed.

switch -regex ($someString.ToLower())
{
    "y(es)?" { "You entered Yes." }
    default { "You entered No." }
}

PowerShell switch documentation: Using the Switch Statement

Gilbertine answered 16/8, 2010 at 13:52 Comment(4)
This is a great solution, although "technically" since I was asking to use separate values, I've marked fletcher as the answer.Winger
Fair enough, although a different regular expression could probably do the same thing.Gilbertine
Regex approach would be more concise.Confute
You don't actually need the ToLower() because equality of strings is case insensitive by default in PowerShell.Variegation
J
11
switch($someString.ToLower())
{
    "yes"   { $_ = "y" }
    "y"     { "You entered Yes." }
    default { "You entered No." }
}

You can arbitrarily branch, cascade, and merge cases in this fashion, as long as the target case is located below/after the case or cases where the $_ variable is respectively reassigned.


n.b. As cute as this behavior is, it seems to reveal that the PowerShell interpreter is not implementing switch/case as efficiently as one might hope or assume. For one, stepping with the ISE debugger suggests that instead of optimized lookup, hashing, or binary branching, each case is tested in turn, like so many if-else statements. (As such, consider putting your most common cases first.) Also, as shown in this answer, PowerShell continues testing cases after having satisfied one. And cruelly enough, there even happens to be a special optimized 'switch' opcode available in .NET CIL which, because of this behavior, PowerShell can't take advantage of.

Jefferey answered 28/11, 2016 at 11:0 Comment(4)
It's not cute- it's documented. Add a break statement if you don't want subsequent branches evaluated.Mors
@Mors Fair enough, "cute" may not have been the best word for referring to a keyword behavior that's altered or atypical vis-a-vis its historical semantics in numerous languages since 'C' (1975) or earlier.Jefferey
@GlennSlayden well I mean it's literally more powerful, where the alternative for certain usecases falls back to exhaustive hard-coding. It's a pretty basic and unsurprising change people learning the language will just be aware of, as opposed to people just picking it up without trying to deliberately learn, relying solely on their past knowledge.Flavour
That's how terrible code is written, frankly, as opposed to being clean & idiomatic in new technologies. Those that are overly comfortable in older languages can stay there, right? If you're implying well-thought-out changes are in any way bad, considering @Mors said they are indeed documented.Flavour
H
11

A slight modification to derekerdmann's post to meet the original request using regex's alternation operator "|"(pipe).

It's also slightly easier for regex newbies to understand and read.

Note that while using regex, if you don't put the start of string character "^"(caret/circumflex) and/or end of string character "$"(dollar) then you may get unexpected/unintuitive behavior (like matching "yesterday" or "why").

Putting grouping characters "()"(parentheses) around the options reduces the need to put start and end of string characters for each option. Without them, you'll get possibly unexpected behavior if you're not savvy with regex. Of course, if you're not processing user input, but rather some set of known strings, it will be more readable without grouping and start and end of string characters.

switch -regex ($someString) #many have noted ToLower() here is redundant
{
        #processing user input
    "^(y|yes|indubitably)$" { "You entered Yes." }

        # not processing user input
    "y|yes|indubitably" { "Yes was the selected string" } 
    default { "You entered No." } 
}
Homing answered 16/3, 2017 at 19:7 Comment(0)
K
6

Supports entering y|ye|yes and case insensitive.

switch -regex ($someString.ToLower()) {
        "^y(es?)?$" {
            "You entered Yes." 
        }
        default { "You entered No." }
}
Koppel answered 16/8, 2010 at 16:39 Comment(1)
Actually, your expression "[yes]" matches any occurrence of the characters 'y', 'e', or 's' anywhere in $someString. Even if $someString is "no! no! no!s" that switch block will return "You entered Yes." because of the trailing 's'. To match y|ye|yes the expression should be "^y(es?)?$".Auerbach
B
6

Here's another one. Switch is case insensitive anyway. -eq with an array on the left will return the thing it's equal to, and anything returned is true.

switch($someString)
{
  { 'y', 'yes' -eq $_ } { 'You entered Yes.' }
  default               { 'You entered No.'  }
}
Bealle answered 16/7, 2021 at 21:4 Comment(0)
C
4

The answer given by js2010 has value, especially in the context of the question. But, in the context of people searching for examples of how to use the Switch statement, it could use an explanation of why it works, and a couple of potential problems.

The -eq operator, with an array on the left, will filter out only items that match the single value given on the right. If a single value is returned, then the results is a single value, but if more than one value is returned, then the results is an array.

The Switch statement creates a context where a Boolean value is expected, and, in that context, the results of the -eq comparison is determined to be $false if it is '', "", $null, 0, or 0.0 in value, else, it is considered to be $true. This means that testing for an empty string will, as in the following example, fail. The following example also shows how to do a case sensitive test using -ceq, and shows the sometimes useful behavior of Switch when the break statement isn't used:

foreach ($someString in '', 'C', 'd', 'E') {
    switch($someString) {
        { '', 'b', 'c', 'd', 'g' -eq $_ }   { "-eq: '$_' does Match" }
        { '', 'b', 'c', 'd', 'g' -ceq $_ }  { "-ceq '$_' does Match" }
        default                 { "'$_' doesn't Match" }
    }
}

As you seen in the following example results, even though '' is in the list of items to check for, the result of -eq is a single item of '', which is converted to the Boolean value $false and results in '' being treated as if it isn't in the list.

'' doesn't Match
-eq: 'C' does Match
-eq: 'd' does Match
-ceq 'd' does Match
'E' doesn't Match

The other problem, as in the following example, is when you place the single test value on the left, and the array of items to test against on right.

foreach ($someString in '', 'C', 'd', 'E') {
    switch($someString) {
        { $_ -eq '', 'b', 'c', 'd', 'g' }   { "-eq: '$_' does Match" }
        { $_ -ceq '', 'b', 'c', 'd', 'g' }  { "-ceq '$_' does Match" }
        default                 { "'$_' doesn't Match" }
    }
}

As you can see in the following example results, nothing matches:

'' doesn't Match   
'C' doesn't Match  
'd' doesn't Match  
'E' doesn't Match

These problems are solved by always having the array of items to test against on the left side of the -eq operator, and by having -eq return an array of 2 empty strings when testing for an empty string. The array of empty strings returned by -eq is a non $null value, and is converted to $true.

foreach ($someString in '', 'C', 'd', 'E') {
    switch($someString) {
        { '', '', 'b', 'c', 'd', 'g' -eq $_ }   { "-eq: '$_' does Match" }
        { '', '', 'b', 'c', 'd', 'g' -ceq $_ }  { "-ceq '$_' does Match" }
        default                 { "'$_' doesn't Match" }
    }
}

As you can see in the following example, having -eq and -ceq return an array of empty strings, which is then converted Boolean $true, will correctly match '':

-eq: '' does Match
-ceq '' does Match
-eq: 'C' does Match
-eq: 'd' does Match
-ceq 'd' does Match
'E' doesn't Match

If you aim to use this method with any value that PowerShell evaluates to $false, you should consider the following:

  1. The -eq operator considers these the same: '' and ""
  2. The -eq operator considers these the same: $false, 0, and 0.0.
  3. If you have a variable assigned an empty array, as in $emptyArray = @(), then -eq operator considers these the same: $null and $emptyArray.
Cuneate answered 27/12, 2022 at 18:23 Comment(0)
C
-3

The switch doesn't appear to be case sensitive in PowerShell 5.1. All four of the $someString examples below work. [Fixed, thanks js2010]

$someString = "YES"
$someString = "yes"
$someString = "yEs"
$someString = "y"
$someString = "n"

switch ($someString) {
   {"y","yes" -eq $_} { "You entered Yes." }
   Default { "You didn't enter Yes."}
}
Culprit answered 11/12, 2019 at 13:14 Comment(2)
This is a wrong answer. Correct could be like: $a = 'b'; switch ($a) { {$_ -in 'a', 'b'} {'bingo'}; default {'nope!'} }Brunelle
Nice try, but {"y","yes"} would be true for any answer. Another alternative: {"y","yes" -eq $_}Bealle
G
-5

After searching a solution for the same problem like you, I've found this small topic here. In advance I got a much smoother solution for this switch, case statement

switch($someString) #switch is caseINsensitive, so you don't need to lower
{
    { 'y' -or 'yes' } { "You entered Yes." }
    default { "You entered No." }
}
Gulf answered 3/7, 2015 at 11:5 Comment(1)
{'y' -or 'yes'} always evaluates to true so the default branch is never hitGt

© 2022 - 2024 — McMap. All rights reserved.