当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 实用 PowerShell:分支

作者:精品下载站 日期:2024-12-14 04:13:58 浏览:14 分类:玩电脑

实用 PowerShell:分支


在实用 PowerShell 系列的上一篇文章中,我讨论了循环作为流控制的一部分。在本文中,我将介绍流程控制的另一个重要部分:分支。根据条件,分支定义了代码要遵循的多个路径。

如果-那么-否则

除了循环结构之外,还有用于控制代码分支的决策 cmdlet。它是一个基本的构造,允许您根据满足的条件(if)(then)和可选的不满足的条件(else)来分支代码。这种结构很容易理解和使用,因为它遵循自然语言。您还会在其他环境中看到它,例如家庭自动化或 PowerAutomate。在下面的示例中,我们将处理禁用尚未禁用的共享邮箱帐户的邮箱:

$Mailboxes= Get-ExoMailbox -ResultSize Unlimited -Properties AccountDisabled

ForEach( $Mailbox in $Mailboxes) {
 If( $Mailbox.RecipientTypeDetails -ieq 'SharedMailbox') {
 If(-not $Mailbox.AccountDisabled) {
 Write-Host( 'Disabling account of shared mailbox {0}' -f $Mailbox.userPrincipalName)
 }
 Else {
 Write-Host( 'Account of shared mailbox {0} already disabled' -f $Mailbox.userPrincipalName)
 }
 }
 Else {
 Write-Host ('{0} is not a shared mailbox' -f $Mailbox.userPrincipalName)
 }
}

请注意,比较运算符 ieq 代表不区分大小写的相等。这是 eq 的默认模式,但为了清楚起见,我习惯性地提及它。

如示例所示,您可以嵌套 If cmdlet,Else 部分是可选的,但通常作为占位符添加,并带有描述性注释以供参考。当条件变得复杂时,保持概览就成为一项挑战,并且您可能会不匹配括号或引号。这就是 Visual Studio Code 甚至 PowerShell ISE 等编辑器发挥作用的地方,当鼠标悬停在括号上时,指示(当前)匹配括号的位置,从而更容易发现丢失的左括号或右括号或引号。 Visual Studio Code 还允许您折叠(或展开)if 或 else 分支,如图 1 所示。

[玩转系统] 实用 PowerShell:分支

转变

正如您在 If-Then-Else 示例中看到的那样,当使用多个条件或嵌套时,代码可能会变得非常复杂并且不可读。这就是 Switch 结构派上用场的地方。 Switch 结构允许你改变这个:

If( $Mailbox.recipientTypeDetails -ieq 'UserMailbox') {
 # code for UserMailbox
ElseIf( $Mailbox.recipientTypeDetails -ieq 'SharedMailbox') {
 # code for SharedMailbox
Else {
 # code for not UserMailbox nor SharedMailbox
}

进入这个:

Switch( $Mailbox.recipientTypeDetails) {
 'UserMailbox' {
 # code for UserMailbox
 }
 'SharedMailbox' {
 # code for SharedMailbox
 }
 default {
 # code for not UserMailbox nor SharedMailbox
 Write-Warning ('Unsupported type: {0}' -f $_)
 }
}

$Mailbox.recipientTypeDetails 值根据“UserMailbox”和“SharedMailbox”进行检查。当找到匹配项时,将运行相应的脚本块。如果未找到匹配项,则执行可选的默认代码。变量 $_ 被分配了在开关内匹配的值,我们用它来报告我们遇到了不受支持的recipientTypeDetails属性值。

Switch 还有一些操作修饰符,例如开关 -regex 和 -wildcard。这些允许 Switch 根据正则表达式或通配符(类似于 -like)检查评估的对象。请注意,原则上每个条件都会被评估,这可能会导致多个脚本块被执行。例如,在以下显示通配符模式下 Switch 的片段中,当recipientTypeDetails 为 UserMailbox 时,UserMailbox 和 *Mailbox 的代码都将运行:

Switch -wildcard ( $Mailbox.recipientTypeDetails) {
 'UserMailbox' {
 # code for UserMailbox
 }
 '*Mailbox' {
 # code for any type ending in Mailbox
 }
}

可以通过在代码块末尾添加中断来避免进一步评估,例如

Switch -wildcard ( $Mailbox.recipientTypeDetails) {
 'UserMailbox' {
 # code for UserMailbox
 break
 }
 '*Mailbox' {
 # code for any type ending in Mailbox
 break
 }
}

Switch cmdlet 功能强大,可以防止代码陷入过于复杂的 if-then-else 分支,同时保持可读性。例如,它还允许您匹配多个值。有关开关和其他用法的更多信息,请参阅此处。

停止 Cmdlet

到目前为止,我们已经看到了按顺序执行代码的结构。在某些情况下,您可能想要缩短分支或跳过迭代。例如,当您循环遍历元素以查找匹配项时,找到匹配项后无需继续循环的其余部分。这时可以使用Break。另一方面,如果您不想处理循环中的某个元素,则可以使用Continue

与 Break 一样,停止 cmdlet 也可在函数和脚本级别使用,其形式为“Exit”和“Return”。 Exit 在调用函数或脚本的范围内终止该函数或脚本。 Return 退出脚本或函数,可选择将对象返回到调用它的范围,之后将继续执行。要了解这些 cmdlet 的不同效果,请查看以下示例:

Function Test {
 For( $i=0; $i -lt 4; $i++) {
 Write-Host ('Start {0}' -f $i)
 If( $i -eq 2) {

 # Aborts the loop
 Break

 # Skips iteration, continues with next iteration
 Continue

 # Aborts execution, continues at caller scope
 Return

 # Exits and at scope of caller
 Exit
 }
 Write-Host ('End {0}' -f $i)
 }
 Write-Host ('End Function.')
}

Test
Write-Host ('End Script.')

可以通过对上面脚本中未测试的 cmdlet 进行注释来演示使用每个停止 cmdlet 的效果。下表显示了每个未注释的停止 cmdlet 的输出。

BreakContinueReturnExitStart 0
End 0
Start 1
End 1
Start 2
End Function.
End Script.Start 0
End 0
Start 1
End 1
Start 2
Start 3
End 3
End Function.
End Script.Start 0
End 0
Start 1
End 1
Start 2
End Script.Start 0
End 0
Start 1
End 1
Start 2

毕竟,现在我强烈建议避免停止 cmdlet,而是对循环或函数使用单点退出。那些支持使用停止 cmdlet 的人通常声称它们可以提高效率和性能。虽然对于非结构化代码可能确实如此,但当您正确构建代码时,就没有真正需要添加停止 cmdlet。此外,停止 cmdlet 会使代码更难以阅读和理解。许多组织使用代码风格指南,强制使用单一退出点。

一元运算符

在一些示例中,我使用一元运算符。一元运算符允许您编写 $i++ 而不是 $i=$i+1。您还可以通过编写 ++$i 在评估变量之前添加一元运算符,在这种情况下,在评估变量之前将 1 添加到 $i 中。您可以使用“-”进行减法,例如 $i-。您还可以使用一元运算符来执行其他算术运算,例如$i+=2 会将 2 添加到 $i,$i*=4 会将 $i 中存储的值乘以 4。有关一元运算符的更多信息,请参阅此处。

一元运算符的常见用法是字符串连接或将项目“添加”到数组或对象集合中,例如 $string+= $field 或 $array+= $item。然而,在幕后,此代码执行为 $array= $array + $item,它复制 $array 对象并添加 $item。这不仅对于较大的复杂对象来说可能会很慢。它还解释了为什么这适用于数组,根据定义,数组具有固定数量的元素(以及为什么我提到使用引号“添加”)。

字符串或数组等对象类型有更好的替代 (.NET) 对象类型,它们更适合密集型数据操作,例如用 [System.Collections.ArrayList] 代替 Array,或用 [System.Text.StringBuilder] 代替字符串。这是一个快速比较,以便您了解:

# Add 10.000 numbers to an array
PS> $Array= @()
PS> Measure-Command {
 1..10000 | ForEach{ $Array+= $_ }
 } | Select TotalMilliseconds

 TotalMilliseconds
 -----------------
 21746.56

# Add 10.000 numbers to arraylist
PS> $ArrayList= [System.Collections.ArrayList]::new()
PS> Measure-Command {
 1..10000 | ForEach{ $ArrayList.Add( $_)| Out-Null }
 } | Select TotalMilliseconds

 TotalMilliseconds
 -----------------
 77.69

运行上面的示例,您会注意到,在使用其内置的 Add 方法时,ArrayList 明显比使用 Array 快。请注意,ArrayList 的 Add 返回刚刚添加的元素的编号,因此我在示例中通过将输出管道传输到 Out-Null 来抑制该情况。现在,我并不是说您需要更改所有代码,但对于处理较大数据集的脚本来说,性能优势是相当可观的。

动态执行的分支

与循环一样,分支是基本编程结构之一。它为您的代码添加了动态性,让您的代码根据条件决定执行什么。此外,当嵌套的 if-then-else 语句变得过于复杂时,熟悉 switch cmdlet 将有所帮助,并让您保持可读性和可维护性。

如果您有疑问或意见,请随时在评论中联系。如果没有的话,直到下一篇文章!

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯