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

[玩转系统] PowerShell ForEach-Object 和 ForEach 循环解释

作者:精品下载站 日期:2024-12-14 03:45:17 浏览:14 分类:玩电脑

PowerShell ForEach-Object 和 ForEach 循环解释


PowerShell ForEach 和 ForEach-Objects 循环是最常用的函数之一。它们使您可以轻松地迭代集合。但是 ForEach-ObjectForEach() cmdlet 之间有什么区别呢?

这两个 cmdlet 看起来很相似,基本上,它们允许您执行相同的操作。但了解两者之间的区别可以让您改进 PowerShell 脚本。

在本文中,我们将了解如何使用 PowerShell ForEach 和 ForEach-Object 并解释两者之间的区别。

ForEach() 与 ForEach-对象

PowerShell ForEach()ForEach-Object cmdlet 都允许您迭代集合并对其中的每个项目执行操作。然而,它们处理数据的方式和可用性方面存在差异。

ForEach() cmdlet 在开始处理项目之前将整个集合加载到内存中。通过将集合加载到计算机内存中,它比 ForEach-Object cmdlet 更快。

foreach ($fruit in $fruitsAvailable) {
    Write-Host $fruit
}

另一方面,PowerShell ForEach-Object cmdlet 还可以将其通过管道传输到另一个 cmdlet 后面。这样它就可以处理管道中的每个项目。这也意味着您可以在其后面通过管道传输其他 cmdlet。

$fruitsAvailable | ForEach-Object {
    Write-Host $_
}

除了这两个 cmdlet 之外,我们还有两个别名 ForEach (注意,没有括号 () )和 %。两者都是 ForEach-Object cmdlet 的别名,但这有时可能会有点令人困惑。下面的命令都是一样的:

$fruitsAvailable | ForEach-Object {
    Write-Host $_
}

$fruitsAvailable | ForEach {
    Write-Host $_
}

$fruitsAvailable | % {
    Write-Host $_
}

现在,对于小型集合,性能差异和内存使用量很小。在上面的示例中,ForEach() 语句仅快了 4 毫秒,并且脚本块更易于阅读。但在处理大型集合时,您可能需要考虑内存使用情况。

例如,当您加载大型 CSV 文件时,在其后面通过管道传输 ForEach-Object cmdlet 比首先将结果存储到变量中更有效:

Import-Csv -Delimiter ";" -Path c:\temp\EmailAddresses.csv | Foreach-Object {Get-ADUser -Identity $_.Name}

PowerShell ForEach

PowerShell 中的 ForEach 语句允许您迭代数组或项目列表。 ForEach 语句的语法非常简单:

foreach ($item in $collection) {
   # Script to execute with $item
}

正如您所看到的,我们可以指定要循环的集合,这可以是数组或列表,并且我们为迭代器指定一个变量。这将包含集合内每个项目的值。

例如,要将每个水果打印到控制台,我们可以执行以下操作:

$fruitsAvailable = @("Apple", "Banana", "Orange", "Grapes", "Mango", "Strawberry", "Pineapple")

foreach ($fruit in $fruitsAvailable) { 
   Write-Host $fruit 
}

[玩转系统] PowerShell ForEach-Object 和 ForEach 循环解释

使用集合

我们还可以将 PowerShell ForEach 语句与返回集合的 cmdlet 结合使用。大多数人都会在 cmdlet 后面通过管道传输 ForEach-Object cmdlet,但这并不总是必要的。使用 ForEach 语句的优点是与 ForEach-Object cmdlet 相比更易于阅读。

例如,Get-ChildItem cmdlet 返回给定路径中的所有文件和文件夹。要迭代文件,我们可以执行以下操作:

foreach ($item in Get-ChildItem -path "c:\temp") {
    Write-Host $item.Name
}

正如您所看到的,Get-ChildItem 返回文件或文件夹对象。在脚本块内,我们可以访问项目的所有属性,在本例中为项目名称。

中断并继续

在 PowerShell 中使用 ForEach 循环时,您有时希望在找到某个值时停止迭代或根据其值跳过某个项目。为此,我们可以使用 BreakContinue 关键字。

例如,如果我们想检查一个值是否可以被 7 整除。如果不是,那么我们继续下一项:

foreach ($i in 1..100) {
    if ($i % 7 -ne 0 ) { continue }
    Write-Host "$i is a multiple of 7"
}

Break关键字允许您在满足特定条件时停止 PowerShell foreach 循环。现在我不经常使用这些,大多数时候您可以通过使用 where-object cmdlet 或使用适当的过滤器来编写更好的代码。但下面的示例可让您了解如何使用 Break 关键字:

foreach ($number in 1..10) {
    if ($number -gt 5) {
        Write-Host "Exiting the loop because $number is greater than 5"
        break
    }

    Write-Host "Processing number: $number"
}

嵌套 ForEach 循环

在脚本块内,您也可以使用其他 foreach 语句。尽量将其保持在最低限度,因为当您使用太多嵌套的 foreach 循环时,您的代码将变得更难以阅读和调试。

拿下面的例子来说,我们有一组球队,每个球队内部都有一组球员。外部 foreach 循环迭代 $teams 数组中的每个团队。然后,内部 foreach 循环会迭代每个团队内的球员。

# Define an array of teams, where each team has an array of players
$teams = @(
    @{
        TeamName = "Team A"
        Players  = @("Player1A", "Player2A", "Player3A")
    },
    @{
        TeamName = "Team B"
        Players  = @("Player1B", "Player2B", "Player3B")
    },
    @{
        TeamName = "Team C"
        Players  = @("Player1C", "Player2C", "Player3C")
    }
)

# Nested foreach loop to iterate over teams and players
foreach ($team in $teams) {
    Write-Host "Team: $($team.TeamName)"

    foreach ($player in $team.Players) {
        Write-Host "  Player: $player"
    }
}

PowerShell ForEach-对象

当您想要迭代从管道流式传输的项目时,可以使用 PowerShell ForEach-Object cmdlet。 ForEach-Object 的优点是,它会在项目沿着管道传输时开始对其进行处理,并且您还可以在其后面通过管道传输其他 cmdlet。

当您处理大型集合或数据集时,您需要使用此 cmdlet,因为尽管它比 foreach 语句慢,但它不会消耗那么多内存。

ForEach-Object 的另一个优点是,您可以通过使用 PowerShell 7 中的并行开关来加快速度。这允许您同时处理多个项目,稍后会详细介绍。

如前所述,ForEach-Object cmdlet 通过管道传输到另一个 cmdlet 后面。您可以使用一个(或多个)脚本块来指定要对当前项执行的操作。例如,要获取正在运行的进程的所有进程名称,您可以执行以下操作:

Get-Process | ForEach-Object {$_.ProcessName}

正如您在上面的示例中看到的,我们使用 $_ 变量引用当前项。

[玩转系统] PowerShell ForEach-Object 和 ForEach 循环解释

您可以在 PowerShell ForEach-Object 脚本块后面通过管道传输其他 cmdlet。因此,如果您想将每个进程名称存储在文本文件中,您可以执行以下操作:

Get-Process | ForEach-Object {$_.ProcessName} | Out-File "c:\temp\runningprocs.txt"

在上面的示例中,我们仅执行脚本块中的一个小脚本,这允许我们将所有内容写在一行中。但您也可以在脚本块内添加更大的脚本:

$files = Get-ChildItem -Path "C:\temp\" -File

$files | ForEach-Object {
    $fileName = $_.Name
    $fileSizeKB = [math]::Round($_.Length / 1KB, 2)

    Write-Host "Processing file: $fileName"
    Write-Host "File size: $fileSizeKB KB"

    Write-Host "Processing complete for $fileName"
}

开始、结束和多个脚本块

使用 ForEach-Object cmdlet 时,可以使用 beginend 脚本块。这些脚本块仅执行一次,而普通脚本块(进程脚本块)针对集合中的每个项目执行。

当您需要注册脚本的开始和结束时间,或者当您想要注册进程的开始或结束时,开始和结束脚本块非常有用:

1..5 | ForEach-Object -Begin {
    Write-Host "Starting the processing..."
} -Process {
    $squared = $_ * $_
    Write-Host "Number: $_, Squared: $squared"
} -End {
    Write-Host "Processing complete."
}

在上面的示例中,我们已经标记了每个脚本块的内容:开始块、过程块和结束块。但您不需要指定这一点(我建议这样做,因为它使您的代码更具可读性)。

如果指定多个脚本块,则第一个块始终被视为开始块。

1..2 | ForEach-Object { 'Begin Block' } { 'Process Block' }

# Result
Begin Block
Process Block
Process Block

如果有两个以上的块,则最后一个始终被视为结束块。中间的每个块都是一个进程块。

1..2 | ForEach-Object { 'Begin' } { 'Process A Block' } { 'Process B Block' } { 'End' }

# Result
Begin
Process A Block
Process B Block
Process A Block
Process B Block
End

如果您想运行多个进程块,但不想使用 begin 或 end 块,那么您必须指定参数 begin、process 和 end,并将 $null 值映射到第一个和第二个:

1..2 | ForEach-Object -Begin $null -Process { 'Process A Block' } { 'Process B Block' } -End $null

# Result
Process A Block
Process B Block
Process A Block
Process B Block

使用并行处理

如果您的脚本块非常慢或者需要处理大量项目 (10.000+),那么您可以在 PowerShell 7.x 及更高版本中使用 -parallel 参数。这样,脚本块将为每个项目并行运行,从而允许您同时处理多个项目。

默认情况下,并行参数将以 5 个为一批处理项目。但是使用 -Trottelimit 参数,我们可以定义要使用的运行空间的数量。下面的示例将按 4 个批次处理 8 个项目:

1..8 | ForEach-Object -Parallel {
    Write-Host "Slow script to process $_"
    Start-Sleep 1
} -ThrottleLimit 4

使用并行参数,将为每个批次创建一个新的运行空间。这意味着如果您需要引用脚本块外部的变量,则需要使用 $using: 变量来引用它:

$Message = "Slow script to process:"

1..8 | ForEach-Object -Parallel {
    Write-Host $using:Message $_
    Start-Sleep 1
} -ThrottleLimit 4

现在要知道的是,并行运行脚本并不总是更快。老实说,98% 的时间并没有更快。并行执行脚本块的问题在于创建运行空间来执行脚本需要时间。

如果没有并行函数,脚本块将在当前的 PowerShell 线程中执行。这样它就可以访问所有变量、管道和加载的内存。但要并行执行脚本块,需要启动一个新的 PowerShell 线程(创建新的运行空间),这需要时间(大约最多 1 秒)。

因此,只有当您的脚本块非常慢、CPU 密集型或者需要等待 API 响应(例如)时,这才有意义。

例如,编写“Hello World”只需要几毫秒:

Measure-Command { Write-Host "Hello World" } | Select TotalMilliseconds

# Result
TotalMilliseconds
-----------------
             4,65

但并行执行时,执行起来几乎要30多毫秒:

Measure-Command { 1 | ForEach-Object -Parallel { Write-Host "Hello World" } } | Select TotalMilliseconds

# Result
TotalMilliseconds
-----------------
             31,36

然而,如果我们有一个脚本需要超过 1 秒才能执行(这里用 start-sleep 进行模拟),那么并行方法将具有优势:

Measure-Command { 1..5 | ForEach-Object { Start-Sleep -Seconds 1 } } | Select TotalMilliseconds

# Result
TotalMilliseconds
-----------------
          5054,55

# Execute in parallel, with the 5 default threads:
Measure-Command { 1..5 | ForEach-Object -Parallel { Start-Sleep -Seconds 1 } } | Select TotalMilliseconds

# Result
TotalMilliseconds
-----------------
          1082,24

[玩转系统] PowerShell ForEach-Object 和 ForEach 循环解释

中断、继续和返回

就像 foreach 语句一样,我们可以使用 Break 来停止 PowerShell ForEach-Object 循环,但我们不能使用 Continue 。中断将完全停止 ForEach-Object 循环中的迭代并退出循环:

1..10 | ForEach-Object {
    if ($_ -gt 5) {
        Write-Host "Exiting the loop because $_ is greater than 5"
        break
    }

    Write-Host "Processing number: $_"
}

但是,如果我们尝试在 ForEach-Object 循环内使用 Continue,那么您将看到它也停止并存在于循环中。所以下面的例子是行不通的:

# This won't work
1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) { continue }
    Write-Host "$_ is a multiple of 7"
}

相反,我们需要使用 return 关键字继续查看集合中的下一项。

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) { return}
    Write-Host "$_ is a multiple of 7"
}

[玩转系统] PowerShell ForEach-Object 和 ForEach 循环解释

总结

在大多数情况下,使用 foreach() 语句是使用 ForEach 循环的最快且最易读的方法。但是,如果您需要处理大量数据,或者需要在其后面通过管道传输其他 cmdlet,那么最好使用 ForEach-Object cmdlet。

请记住,并行函数可能看起来很好用,但在大多数情况下,它会比较慢,因为启动新的运行空间需要时间。所以一定要衡量差异。

希望您喜欢这篇文章,如果您有任何疑问,请在下面发表评论。如果您想阅读更多这些文章,请订阅时事通讯!

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

取消回复欢迎 发表评论:

关灯