关于你想了解的if语句的所有内容

与许多其他语言一样,PowerShell 具有用于在脚本中有条件地执行代码的语句。 其中一个语句是 If 语句。 今天,我们将深入了解 PowerShell 中最基本的命令之一。

注释

本文 的原始版本 出现在 @KevinMarquette撰写的博客上。 PowerShell 团队感谢 Kevin 与我们共享此内容。 请在 PowerShellExplained.com 查看他的博客。

条件执行

脚本通常需要根据这些决策做出决策并执行不同的逻辑。 这就是我所指的条件执行。 你有一个语句或值要计算,然后根据该计算执行其他代码部分。 这正是 if 语句的作用。

if 语句

下面是语句的基本示例:if

$condition = $true
if ( $condition )
{
    Write-Output "The condition was true"
}

if 语句首先要做的是计算括号中的表达式。 如果计算结果为 $true,则执行大括号中的语句。 如果值为 $false,则会跳过该语句块。

在前面的示例中,该 if 语句只是计算变量 $condition 。 它是 $true,本来会在语句块内执行 Write-Output 命令。

在某些语言中,可以在语句之后 if 放置一行代码,然后执行它。 PowerShell 中的情况并非如此。 必须提供完整的 statement block 大括号才能正常工作。

比较运算符

语句的 if 最常见用法是将两个项彼此进行比较。 PowerShell 具有适用于不同比较方案的特殊运算符。 使用比较运算符时,左侧的值与右侧的值进行比较。

-eq 表示相等

-eq 执行两个值的相等性检查,以确保它们彼此相等。

$value = Get-MysteryValue
if ( 5 -eq $value )
{
    # do something
}

在此示例中,我采用已知值 5 并将其与我 $value 进行比较,以查看它们是否匹配。

一个可能的用例是在对某个值执行操作之前检查其状态。 可以获取服务,并在调用 Restart-Service 之前检查其状态是否为正在运行。

其他如 C# 的语言通常使用 == 作为相等运算符(例如:5 == $value),但这在 PowerShell 中不起作用。 人们犯的另一个常见错误是使用等号(例如:5 = $value),这是用于为变量赋值的保留符号。 通过将已知值放在左侧,可以降低发生错误的可能性。

此运算符(和其他运算符)有一些变体。

  • -eq 不区分大小写的相等性
  • -ieq 不区分大小写的相等性
  • -ceq 区分大小写的相等性

-ne 不等于

许多运算符都有一个正在检查相反结果的相关运算符。 -ne 验证值是否不相等。

if ( 5 -ne $value )
{
    # do something
}

使用此来确保仅当值不是 5 时才执行该操作。 一个很好的用例,即在尝试启动服务之前,检查服务是否处于运行状态。

变化:

  • -ne 不区分大小写不等于
  • -ine 不区分大小写, 不相等
  • -cne 区分大小写不相等

这些是反函数的 -eq变体。 列出其他运算符的变体时,我会将这些类型组合在一起。

-gt -ge -lt -le 大于或小于

检查值是否大于或小于另一个值时,会使用这些运算符。 -gt -ge -lt -le代表 GreaterThan、GreaterThanOrEqual、LessThan 和 LessThanOrEqual。

if ( $value -gt 5 )
{
    # do something
}

变化:

  • -gt 大于
  • -igt 大于,不区分大小写
  • -cgt 大于(区分大小写)
  • -ge 大于或等于
  • -ige 大于或等于不区分大小写
  • -cge 大于或等于,区分大小写
  • -lt 少于
  • -ilt 小于,不区分大小写
  • -clt 小于,区分大小写
  • -le 小于或等于
  • -ile 小于或等于,不区分大小写
  • -cle 小于或等于 - 区分大小写

我不知道为什么要对这些运算符使用区分大小写和不区分大小写的选项。

类似通配符匹配类型

PowerShell 有自己的基于通配符的模式匹配语法,你可以将其与运算符一起使用 -like 。 这些通配符模式相当基本。

  • ? 匹配任何单个字符
  • * 匹配任意数量的字符
$value = 'S-ATX-SQL01'
if ( $value -like 'S-*-SQL??')
{
    # do something
}

必须指出模式与整个字符串匹配。 如果需要匹配字符串中间的内容,需要在字符串的两端都放置 *

$value = 'S-ATX-SQL02'
if ( $value -like '*SQL*')
{
    # do something
}

变化:

  • -like 不区分大小写的通配符
  • -ilike 不区分大小写的通配符
  • -clike 区分大小写的通配符
  • -notlike 不区分大小写的通配符未匹配
  • -inotlike 不区分大小写的通配符不匹配
  • -cnotlike 区分大小写的通配符不匹配

-match 正则表达式

使用 -match 运算符可以检查字符串中是否存在正则表达式匹配项。 如果通配符模式不够灵活,则使用此模式。

$value = 'S-ATX-SQL01'
if ( $value -match 'S-\w\w\w-SQL\d\d')
{
    # do something
}

默认情况下,正则表达式模式与字符串中的任何位置匹配。 因此,可以指定想要匹配的子字符串,如下所示:

$value = 'S-ATX-SQL01'
if ( $value -match 'SQL')
{
    # do something
}

正则表达式是其自身的复杂语言,值得研究。 在另一篇文章中,我更详细地谈论< c1>使用正则表达式的多种方式。

变化:

  • -match 不区分大小写的正则表达式
  • -imatch 不区分大小写的正则表达式
  • -cmatch 区分大小写的正则表达式
  • -notmatch 不区分大小写的正则表达式不匹配
  • -inotmatch 不区分大小写的正则表达式不匹配
  • -cnotmatch 区分大小写的正则表达式未匹配

-属于类型

可以使用运算符检查值的类型 -is

if ( $value -is [string] )
{
    # do something
}

如果在处理类或在管道中传递各种对象时,可以使用此方法。 您可以选择将服务或服务名称作为输入。 然后检查是否有服务,如果只有名称,则提取服务。

if ( $Service -isnot [System.ServiceProcess.ServiceController] )
{
    $Service = Get-Service -Name $Service
}

变化:

  • -is 类型
  • -isnot 不是类型

集合运算符

将前面的运算符与单个值一起使用时,结果为 $true$false。 在处理集合时,方法略有不同。 将计算集合中的每个项,运算符将返回计算结果为 $true的每个值。

PS> 1,2,3,4 -eq 3
3

if 语句中,这仍然正常工作。 因此,运算符返回一个值,然后整个语句是 $true

$array = 1..6
if ( $array -gt 3 )
{
    # do something
}

这里有一个小陷阱隐藏在细节中,我需要指出。以这种方式使用 -ne 运算符时,很容易误以为逻辑是反向的。 使用 -ne 与集合,如果集合中的任何项与值不匹配,则返回 $true

PS> 1,2,3 -ne 4
1
2
3

这看起来像一个聪明的技巧,但我们有运算符 -contains ,并 -in 更有效地处理这一点。 符合 -notcontains 的预期表现。

-包含

运算符 -contains 检查集合中是否包含您的值。 一旦找到匹配项,它就会返回 $true

$array = 1..6
if ( $array -contains 3 )
{
    # do something
}

这是查看集合是否包含你的值的首选方法。 每次使用 Where-Object (或 -eq)都会遍历整个列表,显著降低速度。

变化:

  • -contains 不区分大小写的匹配
  • -icontains 不区分大小写的匹配
  • -ccontains 区分大小写的匹配
  • -notcontains 不区分大小写的匹配未成功
  • -inotcontains 大小写不敏感未匹配
  • -cnotcontains 区分大小写不匹配

-in

运算符 -in 就像运算符 -contains,只是集合位于右侧。

$array = 1..6
if ( 3 -in $array )
{
    # do something
}

变化:

  • -in 不区分大小写的匹配
  • -iin 不区分大小写的匹配
  • -cin 区分大小写匹配
  • -notin 不区分大小写不匹配
  • -inotin 大小写不敏感未匹配
  • -cnotin 区分大小写不匹配

逻辑运算符

逻辑运算符用于反转或合并其他表达式。

-不

运算符-not将表达式从$false翻转为$true或从$true翻转为$false。 下面是一个示例,我们希望当Test-Path$false时执行一个动作。

if ( -not ( Test-Path -Path $path ) )

我们讨论的大多数运算符都有一个变体,你不需要使用 -not 运算符。 但有时它仍然有用。

! 操作员

您可以将 ! 用作 -not 的别名。

if ( -not $value ){}
if ( !$value ){}

你可能会看到 ! 的使用频率更高,尤其是来自其他语言(如 C#)的人员。 我更喜欢手动输入,因为我发现快速查看代码时不容易辨认。

-和

您可以将表达式与运算符-and结合使用。 在这种情况下,表达式的双方都需要为$true,以使整个表达式为$true

if ( ($age -gt 13) -and ($age -lt 55) )

在此示例中,$age 的左侧必须是13岁或以上,右侧必须小于55岁。 我添加了额外的括号,以便在该示例中更清晰,但只要表达式简单,它们都是可选的。 去除它们后的相同示例。

if ( $age -gt 13 -and $age -lt 55 )

评估顺序是从左到右。 如果第一项的计算结果为 $false,它将提前退出,并且不执行正确的比较。 在使用某个值之前,需要确认值是否存在,这样会很方便。 例如,Test-Path 如果提供的是$null路径,则会抛出异常。

if ( $null -ne $path -and (Test-Path -Path $path) )

-或

-or这允许你指定两个表达式,如果其中一个表达式是$true,则返回 $true

if ( $age -le 13 -or $age -ge 55 )

-and运算符一样,求值从左到右进行。 除非第一部分是 $true,则整个语句是 $true 且不会处理表达式的其余部分。

另请注意这些运算符的语法的工作原理。 需要两个单独的表达式。 我看到用户尝试这样做 $value -eq 5 -or 6,却没有意识到自己的错误。

-xor 独占或

这有点不寻常的。 -xor 只允许一个表达式求值为 $true。 因此,如果两个项均为 $false 或两个项 $true,则整个表达式为 $false。 另一种理解方式是,仅当表达式的结果不同时时,该表达式才为$true

很少有人会使用此逻辑运算符,我不能想出一个很好的例子,为什么我会使用它。

位运算符

按位运算符对值中的各个位进行操作,并产生一个新的结果值。 教学 位运算符 超出了本文的范围,但下面是它们的列表。

  • -band 二进制与运算
  • -bor 二进制或
  • -bxor binary exclusive OR
  • -bnot binary NOT
  • -shl 向左移动
  • -shr 右移

PowerShell 表达式

我们可以在条件语句中使用标准的 PowerShell。

if ( Test-Path -Path $Path )

Test-Path 执行时返回 $true$false。 这也适用于返回其他值的命令。

if ( Get-Process Notepad* )

如果有返回的进程,其结果为 $true;如果没有,结果为 $false。 使用管道表达式或其他 PowerShell 语句是完全有效的,如下所示:

if ( Get-Process | where Name -EQ Notepad )

这些表达式可以与-and-or运算符组合,但可能需要使用括号将表达式分成子表达式。

if ( (Get-Process) -and (Get-Service) )

检查$null值

if语句中,没有结果或 $null 值会被评估为 $false 。 在特别检查$null时,最佳实践是将$null放在左侧。

if ( $null -eq $value )

在 PowerShell 中处理 $null 值时,有相当多的细微差别。 如果你对深入潜水感兴趣,我有一篇关于 你想了解的关于$null的文章

条件中的变量赋值

我几乎忘记添加这个,直到普拉松卡鲁南 V提醒了我。

if ($process=Get-Process notepad -ErrorAction Ignore) {$process} else {$false}

通常,向变量赋值时,该值不会传递到管道或控制台。 在子表达式中执行变量赋值时,它会传递到管道。

PS> $first = 1
PS> ($second = 2)
2

查看$first作业分配没有输出,而$second作业分配有输出的区别吗? 当一个if语句中的赋值完成时,它将像上面的$second赋值那样执行。 下面是一个关于如何使用它的清晰示例:

if ( $process = Get-Process Notepad* )
{
    $process | Stop-Process
}

如果 $process 被赋予一个值,则语句为 $true,并且 $process 停止。

请确保不要将其与 -eq 混淆,因为这不是相等性检查。 这是一个更不为人知的功能,大多数人没有意识到它是这样运作的。

语句块中的变量赋值

还可以使用 if 语句语句块向变量赋值。

$discount = if ( $age -ge 55 )
{
    Get-SeniorDiscount
}
elseif ( $age -le 13 )
{
    Get-ChildDiscount
}
else
{
    0.00
}

每个脚本块都将编写命令的结果或值作为输出。 我们可以将 if 语句 $discount 的结果分配给变量。 该示例可能同样轻松地将这些值直接分配给 $discount 每个语句块中的变量。 我不能说我经常用这个 if 语句, 但我确实有一个示例, 我最近使用了这个。

备用执行路径

if 语句不仅允许你在语句为 $true 时指定一个动作,还允许在 $false 时指定动作。 这就是else语句开始发挥作用的地方。

使用时,该 else 语句始终是语句的最后一 if 部分。

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
else
{
    Write-Warning "$path doesn't exist or isn't a file."
}

在此示例中,我们检查 $path 以确保它是一个文件。 如果找到该文件,我们将移动该文件。 否则,我们将编写警告。 这种类型的分支逻辑非常常见。

嵌套 if 语句

ifelse语句接受一个脚本块,因此我们可以在其中放置任何 PowerShell 命令,包括另一个if语句。 这样,就可以使用更复杂的逻辑。

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
else
{
    if ( Test-Path -Path $Path )
    {
        Write-Warning "A file was required but a directory was found instead."
    }
    else
    {
        Write-Warning "$path could not be found."
    }
}

在此示例中,我们首先测试理想路径,然后对其采取措施。 如果失败,我们将进行另一个检查,并向用户提供更详细的信息。

elseif

我们不仅限于进行单一条件的检查。 我们可以将if语句和else语句通过使用elseif语句组合在一起,而不是嵌套它们。

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
elseif ( Test-Path -Path $Path )
{
    Write-Warning "A file was required but a directory was found instead."
}
else
{
    Write-Warning "$path could not be found."
}

执行从上到下进行。 首先评估 "top if" 语句。 $false如果是,则它会向下移动到下一个elseifelse列表中。 最后 else 是在其他选项都没有返回 $true 时要采取的默认操作。

开关

此时,我需要提及switch声明。 它提供了一种语法替代方案,可以用于对多个值进行比较。 switch使用该表达式,可以指定一个表达式,并将结果与多个不同的值进行比较。 如果其中一个值匹配,则执行匹配的代码块。 查看此示例:

$itemType = 'Role'
switch ( $itemType )
{
    'Component'
    {
        'is a component'
    }
    'Role'
    {
        'is a role'
    }
    'Location'
    {
        'is a location'
    }
}

有三个可能的值可以匹配 $itemType。 在本例中,它与 Role相匹配。 我使用了一个简单的示例,只是为了让你熟悉 switch 运算符。 我在另一篇文章中更详细地讨论了你想知道的关于 switch 语句的一切

数组内嵌

我有一个名为 Invoke-SnowSql 的函数,该函数使用多个命令行参数启动可执行文件。 这里是该函数中的一个片段,我在其中构建参数数组。

$snowSqlParam = @(
    '--accountname', $Endpoint
    '--username', $Credential.UserName
    '--option', 'exit_on_error=true'
    '--option', 'output_format=csv'
    '--option', 'friendly=false'
    '--option', 'timing=false'
    if ($Debug)
    {
        '--option', 'log_level=DEBUG'
    }
    if ($Path)
    {
        '--filename', $Path
    }
    else
    {
        '--query', $singleLineQuery
    }
)

变量$Debug$Path是最终用户提供的函数的参数。 我在数组的初始化中内联计算它们。 如果 $Debug 为 true,则这些值位于 $snowSqlParam 正确的位置。 变量 $Path 也是如此。

简化复杂操作

你不可避免地会遇到一种情况,比较过多导致难以检查,并且 if 语句会滚动到屏幕右侧的外面。

$user = Get-ADUser -Identity $UserName
if ( $null -ne $user -and $user.Department -eq 'Finance' -and $user.Title -match 'Senior' -and $user.HomeDrive -notlike '\\server\*' )
{
    # Do Something
}

他们可能很难阅读,因此你更容易出错。 我们可以对此做一些事情。

行延续

PowerShell 中有一些运算符可让你将命令包装到下一行。 逻辑运算符 -and-or 是将表达式分解为多行时使用的良好运算符。

if ($null -ne $user -and
    $user.Department -eq 'Finance' -and
    $user.Title -match 'Senior' -and
    $user.HomeDrive -notlike '\\server\*'
)
{
    # Do Something
}

那里仍然有很多事情, 但把每一块放在自己的线上会有很大的区别。 通常,当我进行超过两个的比较或需要滚动到右侧以阅读任何逻辑时,我会使用这种方法。

预先计算结果

我们可以将该语句从语句中 if 取出,并仅检查结果。

$needsSecureHomeDrive = $null -ne $user -and
    $user.Department -eq 'Finance' -and
    $user.Title -match 'Senior' -and
    $user.HomeDrive -notlike '\\server\*'

if ( $needsSecureHomeDrive )
{
    # Do Something
}

这感觉比前面的示例更清爽。 你还有机会使用一个变量名称来解释你真正检查的内容。 这也是一个自解释代码的示例,省去了不必要的注释。

多个 if 语句

我们可以将其分解为多个语句,并逐个检查它们。 在这种情况下,我们使用标志或跟踪变量来合并结果。


$skipUser = $false

if( $null -eq $user )
{
    $skipUser = $true
}

if( $user.Department -ne 'Finance' )
{
    Write-Verbose "isn't in Finance department"
    $skipUser = $true
}

if( $user.Title -match 'Senior' )
{
    Write-Verbose "Doesn't have Senior title"
    $skipUser = $true
}

if( $user.HomeDrive -like '\\server\*' )
{
    Write-Verbose "Home drive already configured"
    $skipUser = $true
}

if ( -not $skipUser )
{
    # do something
}

我确实必须反转逻辑才能使标志逻辑正常工作。 每个评估都是单个 if 语句。 这样做的优点是,在调试时,你可以确切地了解逻辑的具体操作。 我能够同时增加更好的详细程度。

明显的缺点是,编写代码要多得多。 代码看起来更复杂,因为它将一行逻辑扩展为 25 行或更多行。

使用函数

还可以将所有验证逻辑移到函数中。 看看这看起来有多干净。

if ( Test-SecureDriveConfiguration -ADUser $user )
{
    # do something
}

你仍然需要创建函数来执行验证,但它使此代码更易于使用。 它使此代码更易于测试。 在测试中,可以模拟对 Test-ADDriveConfiguration 的调用,并且这个函数只需要两个测试。 一个返回 $true 的情况,一个返回 $false 的情况。 测试另一个函数更简单,因为它太小了。

该函数的主体可能仍然是我们从一开始所用的单行代码,或者是我们在最后一节中使用的展开的逻辑。 这适用于这两种方案,并允许稍后轻松更改该实现。

错误处理

使用if语句的一个重要目的是在发生错误之前检查错误条件。 一个很好的示例是在尝试创建文件夹之前检查文件夹是否已存在。

if ( -not (Test-Path -Path $folder) )
{
    New-Item -Type Directory -Path $folder
}

我想说,如果你期望发生例外,那就不是真正的例外。 因此,请检查你的值并尽可能验证你的条件。

如果想要深入了解实际的异常处理,我有一篇文章介绍 你曾经想要了解的关于异常的所有内容

最后的话

if 语句是一个简单的语句,但它是 PowerShell 的基本部分。 在你编写的几乎每一个脚本中,你会发现自己多次使用这个功能。 我希望你比以前更了解。