about_ForEach
应用到: Windows PowerShell 2.0, Windows PowerShell 3.0, Windows PowerShell 4.0
about_Foreach
介绍可用于遍历项集合中的所有项的语言命令。
Foreach 语句(也称为 Foreach 循环)是一种用于逐个访问(迭代)项集合中的一系列值的语言构造。
需要遍历的集合的最简单和最典型的类型是数组。在 Foreach 循环中,通常对数组中的每个项运行一个或多个命令。
语法
以下内容显示 ForEach 语法:
foreach ($<item> in $<collection>){<statement list>}
简化的语法
从 Windows PowerShell® 3.0 开始,简化了语言关键字(如 Where 和 ForEach)的语法。适用于集合成员的比较运算符被视为参数。你可以对集合的成员使用某个方法,而无需将其包含在脚本块中或添加自动变量“$_.”。请考虑以下两个示例:
dir cert:\ -Recurse | foreach GetKeyAlgorithm
dir cert:\ -Recurse | foreach {$_.GetKeyAlgorithm()}
尽管两个命令都有效,但第一个命令在不使用脚本块或 $_. 自动变量的情况下返回结果。方法 GetKeyAlgorithm 视为 ForEach 的参数。第一个命令返回相同的结果,但不带有错误,因为简化的语法不会尝试为未应用指定参数的项返回结果。
在此示例中,Get-Process 属性 Description 将作为 ForEach 语句的参数传递。结果是活动进程的描述。
Get-Process | ForEach Description
命令管道之外的 Foreach 语句
用括号括起来的 Foreach 语句部分包括表示一个变量和要迭代的集合。Windows PowerShell 将在 Foreach 循环运行时自动创建变量 ($<item>)。在每次迭代浏览该循环前,该变量会设置为集合中的某个值。Foreach 语句之后的块 {<statement list>} 包含一组要针对集合中的每个项执行的命令。
示例
例如,以下示例中的 Foreach 循环显示 $letterArray 数组中的值。
$letterArray = "a","b","c","d"
foreach ($letter in $letterArray)
{
Write-Host $letter
}
在此示例中,使用字符串值“a”、“b”、“c”和“d”创建并初始化 $letterArray 数组。Foreach 语句第一次运行时,它将 $letter 变量设置为等于 $letterArray(“a”)中的第一项。然后,它使用 Write-Host cmdlet 显示字母 a。下次通过循环时,$letter 将设置为“b”,依此类推。在 Foreach 循环显示字母 d 后,Windows PowerShell 将退出循环。
整个 Foreach 语句必须显示在单个行上,以在 Windows PowerShell 命令提示符上将其作为某个命令运行。相反,如果你将该命令放置在 .ps1 脚本文件中,则整个 Foreach 语句无需显示在单个行上。
Foreach 语句还可以与返回项集合的 cmdlet 一起使用。在以下示例中,Foreach 语句逐个访问 Get-ChildItem cmdlet 返回的项列表。
foreach ($file in Get-ChildItem)
{
Write-Host $file
}
你可以通过使用 If 语句限制返回的结果来优化该示例。在以下示例中,Foreach 语句执行与前面的示例相同的循环操作,但它添加 If 语句以将结果限制为大于 100 千字节 (KB) 的文件:
foreach ($file in Get-ChildItem)
{
if ($file.length -gt 100KB)
{
Write-Host $file
}
}
在此示例中,Foreach 循环使用 $file 变量的属性来执行比较运算 ($file.length -gt 100KB)。$File 变量包含 Get-ChildItem cmdlet 返回的对象中的所有属性。因此,你不止可以返回一个文件名。在下一个示例中, Windows PowerShell 在语句列表内返回长度和上次访问时间:
foreach ($file in Get-ChildItem)
{
if ($file.length -gt 100KB)
{
Write-Host $file
Write-Host $file.length
Write-Host $file.lastaccesstime
}
}
在此示例中,你并不局限于在语句列表中运行单个命令。
你还可以使用 Foreach 循环外的变量,并递增循环内的变量。以下示例计算大小超过 100 KB 的文件:
$i = 0
foreach ($file in Get-ChildItem)
{
if ($file.length -gt 100KB)
{
Write-Host $file "file size:" ($file.length /
1024).ToString("F0") KB
$i = $i + 1
}
}
if ($i -ne 0)
{
Write-Host
Write-Host $i " file(s) over 100 KB in the current
directory."}
else
{
Write-Host "No files greater than 100 KB in the current
directory."
}
在前面的示例中,$i 变量在循环外设置为 0,并且该变量针对所发现的每个大于 100 KB 的文件在循环内递增。当该循环退出时,If 语句计算 $i 的值以显示所有超过 100 KB 的文件的计数。或者,它显示一条消息,表明没有找到任何超过 100 KB 的文件。
前面的示例还演示了如何格式化文件长度结果:
($file.length / 1024).ToString("F0")
将该值除以 1,024 以显示以千字节而不是字节为单位的结果,然后使用定点格式说明符来格式化所生成的值以从结果中删除任何小数值。0 使格式说明符不显示任何小数位。
命令管道内部的 Foreach 语句
当 Foreach 显示在命令管道中时,Windows PowerShell 使用 foreach 别名,用于调用 ForEach-Object 命令。在命令管道中使用 foreach 别名时,不包含($<collection> 中的 $<item>)语法,就像处理 Foreach 语句一样。这是因为管道中以前的命令提供此信息。在命令管道中使用 foreach 别名的语法时,语法如下所示:
<command> | foreach {<command_block>}
例如,以下命令中的 Foreach 循环显示工作集(内存使用率)大于 20 兆字节 (MB) 的进程。
Get-Process 命令可获取计算机上的所有进程。Foreach 别名对序列中的每个进程执行脚本块中的命令。
IF 语句选择工作集 (WS) 大于 20 兆字节的进程。Write-Host cmdlet 写入进程名称,后跟一个冒号。它按 1 兆字节划分以字节为单位存储的工作集值,以便获取以兆字节为单位的工作集值。然后它将结果从双精度值转换为字符串。它将该值显示为一个带有零个小数 (F0)、一个空间分隔符(“ ”)和“MB”的定点数。
Write-Host "Processes with working sets greater than 20 MB."
Get-Process | foreach {
if ($_.WS -gt 20MB)
{ Write-Host $_.name ": " ($_.WS/1MB).ToString("F0") MB -Separator ""}
}
Foreach 别名还支持开始命令块、中间命令块和结束命令块。开始和结束命令块运行一次,而中间命令块在每次 Foreach 循环逐个访问集合或数组时运行。
当在命令管道中将 Foreach 别名的语法与一组开始、中间和结束命令块一起使用时,该语法如下所示:
<command> | foreach {<beginning command_block>}{<middle
command_block>}{<ending command_block>}
以下示例演示开始、中间和结束命令块的用法。
Get-ChildItem | foreach {
$fileCount = $directoryCount = 0}{
if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}}{
"$directoryCount directories and $fileCount files"}
开始块创建两个变量并将其初始化为 0:
{$fileCount = $directoryCount = 0}
中间块评估 Get-ChildItem 返回的每个项是目录还是文件:
{if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}}
如果返回的项是目录,则 $directoryCount 变量递增 1。如果该项不是目录,则 $fileCount 变量递增 1。结束块在中间块完成其循环操作后运行,然后返回运算的结果:
{"$directoryCount directories and $fileCount files"}
通过使用开始、中间和结束命令块结构和管道运算符,你可以重新编写以前的示例以查找任何大于 100 KB 的文件,如下所示:
Get-ChildItem | foreach{
$i = 0}{
if ($_.length -gt 100KB)
{
Write-Host $_.name "file size:" ($_.length /
1024).ToString("F0") KB
$i++
}
}{
if ($i -ne 0)
{
Write-Host
Write-Host "$i file(s) over 100 KB in the current
directory."
}
else
{
Write-Host "No files greater than 100 KB in the current
directory."}
}
以下示例(可返回在脚本和脚本模块中使用的函数的函数)演示如何在 foreach 脚本块内使用 MoveNext 方法(它的工作原理与 For 循环上的“skip X”类似)和 foreach 变量的 Current 属性,即使存在异常或不一致间隔的函数定义,该定义跨多个行来声明函数名称。如果脚本或脚本模块中所使用的函数中存在注释,该示例也适用。
function Get-FunctionPosition {
[CmdletBinding()]
[OutputType('FunctionPosition')]
param(
[Parameter(Position=0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[Alias('PSPath')]
[System.String[]]
$Path
)
process {
try {
$filesToProcess = if ($_ -is [System.IO.FileSystemInfo]) {
$_
} else {
Get-Item -Path $Path
}
foreach ($item in $filesToProcess) {
if ($item.PSIsContainer -or $item.Extension -notin @('.ps1','.psm1')) {
continue
}
$tokens = $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($item.FullName,([REF]$tokens),([REF]$errors))
if ($errors) {
Write-Warning "File '$($item.FullName)' has $($errors.Count) parser errors."
}
:tokenLoop foreach ($token in $tokens) {
if ($token.Kind -ne 'Function') {
continue
}
$position = $token.Extent.StartLineNumber
do {
if (-not $foreach.MoveNext()) {
break tokenLoop
}
$token = $foreach.Current
} until ($token.Kind -in @('Generic','Identifier'))
$functionPosition = [pscustomobject]@{
Name = $token.Text
LineNumber = $position
Path = $item.FullName
}
Add-Member -InputObject $functionPosition -TypeName FunctionPosition -PassThru
}
}
} catch {
throw
}
}
}
about_Automatic_Variables
about_If
Foreach-Object