about_Pipelines

简短说明

在 PowerShell 中将命令合并到管道中

长说明

管道是由管道操作员 (|) (ASCII 124) 连接的一系列命令。 每个管道操作员将上一个命令的结果发送到下一个命令。

可以将第一个命令的输出作为第二个命令的输入进行处理。 该输出可以发送到另一个命令。 结果是由一系列简单命令组成的复杂命令链或 管道

例如,

Command-1 | Command-2 | Command-3

在此示例中,发出的 对象 Command-1 将发送到 Command-2Command-2 处理对象并将其发送到 Command-3Command-3 处理对象并向下发送管道。 由于管道中没有更多命令,因此结果将显示在控制台上。

在管道中,命令按从左到右的顺序进行处理。 处理作为单个操作进行处理,输出在生成时显示。

下面是一个简单的示例。 以下命令获取记事本进程,然后停止它。

例如,

Get-Process notepad | Stop-Process

第一个命令使用 Get-Process cmdlet 获取表示记事本进程的 对象。 它使用管道运算符 (|) 将进程对象发送到 Stop-Process cmdlet,这会停止记事本进程。 请注意, Stop-Process 命令没有用于指定进程的 NameID 参数,因为指定的进程是通过管道提交的。

此管道示例获取当前目录中的文本文件,仅选择长度超过 10,000 字节的文件,按长度对其进行排序,并在表中显示每个文件的名称和长度。

Get-ChildItem -Path *.txt |
  Where-Object {$_.length -gt 10000} |
    Sort-Object -Property length |
      Format-Table -Property name, length

此管道由四个按指定顺序排列的命令组成。 下图显示了每个命令在传递给管道中的下一个命令时输出的输出。

Get-ChildItem -Path *.txt
| (FileInfo objects for *.txt)
V
Where-Object {$_.length -gt 10000}
| (FileInfo objects for *.txt)
| (      Length > 10000      )
V
Sort-Object -Property Length
| (FileInfo objects for *.txt)
| (      Length > 10000      )
| (     Sorted by length     )
V
Format-Table -Property name, length
| (FileInfo objects for *.txt)
| (      Length > 10000      )
| (     Sorted by length     )
| (   Formatted in a table   )
V

Name                       Length
----                       ------
tmp1.txt                    82920
tmp2.txt                   114000
tmp3.txt                   114000

使用管道

大多数 PowerShell cmdlet 旨在支持管道。 在大多数情况下,可以通过 管道Get cmdlet 的结果传递给同一名词的另一个 cmdlet。 例如,可以通过管道将 cmdlet 的 Get-Service 输出传递给 Start-ServiceStop-Service cmdlet。

此示例管道在计算机上启动 WMI 服务:

Get-Service wmi | Start-Service

对于另一个示例,可以通过管道将 PowerShell 注册表提供程序中的 或 Get-ChildItem 的输出Get-Item传递给 New-ItemProperty cmdlet。 本示例将值为 8124 的新注册表项 NoOfEmployees 添加到 MyCompany 注册表项。

Get-Item -Path HKLM:\Software\MyCompany |
  New-ItemProperty -Name NoOfEmployees -Value 8124

许多实用工具 cmdlet(如 Get-MemberWhere-ObjectSort-ObjectGroup-ObjectMeasure-Object )几乎完全在管道中使用。 可以通过管道将任何对象类型传递给这些 cmdlet。 此示例演示如何按每个进程中打开的句柄数对计算机上的所有进程进行排序。

Get-Process | Sort-Object -Property handles

可以通过管道将对象传递给格式设置、导出和输出 cmdlet,例如 Format-ListFormat-TableExport-ClixmlExport-CSVOut-File

此示例演示如何使用 Format-List cmdlet 显示进程对象的属性列表。

Get-Process winlogon | Format-List -Property *

还可以通过管道将本机命令的输出传递给 PowerShell cmdlet。 例如:

PS> ipconfig.exe | Select-String -Pattern 'IPv4'

   IPv4 Address. . . . . . . . . . . : 172.24.80.1
   IPv4 Address. . . . . . . . . . . : 192.168.1.45
   IPv4 Address. . . . . . . . . . . : 100.64.108.37

重要

成功错误流与其他 shell 的 stdin 和 stderr 流类似。 但是,stdin 未连接到 PowerShell 管道进行输入。 有关详细信息,请参阅 about_Redirection

通过一些练习,你会发现将简单命令合并到管道中可以节省时间和键入,并使脚本编写更高效。

管道的工作原理

本部分介绍如何将输入对象绑定到 cmdlet 参数,以及如何在管道执行期间进行处理。

接受管道输入

若要支持管道,接收 cmdlet 必须具有接受管道输入的参数。 Get-Help将 命令与“完整”或“参数”选项一起使用,以确定 cmdlet 的哪些参数接受管道输入。

例如,若要确定 cmdlet 的哪些参数 Start-Service 接受管道输入,请键入:

Get-Help Start-Service -Full

Get-Help Start-Service -Parameter *

cmdlet 的 Start-Service 帮助显示,只有 InputObjectName 参数接受管道输入。

-InputObject <ServiceController[]>
Specifies ServiceController objects representing the services to be started.
Enter a variable that contains the objects, or type a command or expression
that gets the objects.

Required?                    true
Position?                    0
Default value                None
Accept pipeline input?       True (ByValue)
Accept wildcard characters?  false

-Name <String[]>
Specifies the service names for the service to be started.

The parameter name is optional. You can use Name or its alias, ServiceName,
or you can omit the parameter name.

Required?                    true
Position?                    0
Default value                None
Accept pipeline input?       True (ByPropertyName, ByValue)
Accept wildcard characters?  false

通过管道 Start-Service将对象发送到 时,PowerShell 会尝试将对象与 InputObjectName 参数相关联。

接受管道输入的方法

Cmdlet 参数可以通过以下两种不同方式之一接受管道输入:

  • ByValue:参数接受与预期的 .NET 类型匹配或可转换为该类型的值。

    例如, Start-Service Name 参数接受按值输入的管道。 它可以接受字符串对象或可转换为字符串的对象。

  • ByPropertyName:仅当输入对象具有与参数同名的属性时,参数才接受输入。

    例如,的 Start-Service Name 参数可以接受具有 Name 属性的对象。 若要列出对象的属性,请通过管道将其传递给 Get-Member

某些参数可以通过值或属性名称接受对象,从而更容易从管道中获取输入。

参数绑定

将对象从一个命令管道传递给另一个命令时,PowerShell 会尝试将管道对象与接收 cmdlet 的参数相关联。

PowerShell 的参数绑定组件根据以下条件将输入对象与 cmdlet 参数相关联:

  • 参数必须接受来自管道的输入。
  • 参数必须接受正在发送的对象类型或可转换为预期类型的类型。
  • 命令中未使用 参数。

例如, Start-Service cmdlet 具有许多参数,但其中只有两个参数 NameInputObject 接受管道输入。 Name 参数采用字符串,InputObject 参数采用服务对象。 因此,可以通过管道将字符串、服务对象和具有可转换为字符串或服务对象的属性的对象。

PowerShell 尽可能高效地管理参数绑定。 不能建议或强制 PowerShell 绑定到特定参数。 如果 PowerShell 无法绑定通过管道的对象,则命令将失败。

有关排查绑定错误的详细信息,请参阅本文后面的 调查管道错误

一次一次处理

将对象管道连接到命令非常类似于使用 命令的参数来提交对象。 让我们看一下管道示例。 在此示例中,我们使用管道来显示服务对象的表。

Get-Service | Format-Table -Property Name, DependentServices

在功能上,这类似于使用 的 Format-TableInputObject 参数提交对象集合。

例如,我们可以将服务集合保存到使用 InputObject 参数传递的变量。

$services = Get-Service
Format-Table -InputObject $services -Property Name, DependentServices

或者,我们可以在 InputObject 参数中嵌入 命令。

Format-Table -InputObject (Get-Service) -Property Name, DependentServices

但是,有一个重要的区别。 通过管道将多个对象传递给命令时,PowerShell 一次将一个对象发送到命令。 使用命令参数时,对象将作为单个数组对象发送。 这种微小的差异会产生重大后果。

执行管道时,PowerShell 会自动枚举实现 IEnumerable 接口或其泛型对应接口的任意类型。 枚举项一次通过管道发送一个。 PowerShell 还通过 Rows 属性枚举 System.Data.DataTable 类型。

自动枚举有一些例外。

  • 必须为哈希表、实现 IDictionary 接口的类型或其泛型对应类型调用 GetEnumerator() 方法,并System.Xml。XmlNode 类型。
  • System.String 类实现 IEnumerable,但 PowerShell 不枚举字符串对象。

在以下示例中,通过管道将数组和哈希表传递给 Measure-Object cmdlet,以计算从管道接收的对象数。 数组具有多个成员,哈希表具有多个键值对。 一次只枚举一个数组。

@(1,2,3) | Measure-Object
Count    : 3
Average  :
Sum      :
Maximum  :
Minimum  :
Property :
@{"One"=1;"Two"=2} | Measure-Object
Count    : 1
Average  :
Sum      :
Maximum  :
Minimum  :
Property :

同样,如果将多个进程对象从 Get-Process cmdlet 管道传递给 Get-Member cmdlet,PowerShell 会将每个进程对象(一次一个)发送到 Get-MemberGet-Member 显示 .NET 类 (进程对象的类型) 及其属性和方法。

Get-Process | Get-Member
TypeName: System.Diagnostics.Process

Name      MemberType     Definition
----      ----------     ----------
Handles   AliasProperty  Handles = Handlecount
Name      AliasProperty  Name = ProcessName
NPM       AliasProperty  NPM = NonpagedSystemMemorySize
...

注意

Get-Member 消除重复项,因此,如果对象都是同一类型,则只显示一个对象类型。

但是,如果使用 的 Get-MemberInputObject 参数,则将Get-Member接收 System.Diagnostics.Process 对象的数组作为单个单元。 它显示对象数组的属性。 (注意 System.Object 类型 name.) [] 后面的数组符号 ()

例如,

Get-Member -InputObject (Get-Process)
TypeName: System.Object[]

Name               MemberType    Definition
----               ----------    ----------
Count              AliasProperty Count = Length
Address            Method        System.Object& Address(Int32 )
Clone              Method        System.Object Clone()
...

此结果可能不符合你的预期。 但在你了解它后,你可以使用它。 例如,所有数组对象都具有 Count 属性。 可以使用该函数来计算计算机上运行的进程数。

例如,

(Get-Process).count

请务必记住,通过管道发送的对象一次传递一个。

在管道中使用本机命令

PowerShell 允许在管道中包含本机外部命令。 但是,请务必注意,PowerShell 的管道面向对象,不支持原始字节数据。

管道或重定向输出原始字节数据的本机程序的输出会将输出转换为 .NET 字符串。 此转换可能会导致原始数据输出损坏。

解决方法是使用 cmd.exe /csh -c 调用本机命令,并使用 | 本机 shell 提供的 和 > 运算符。

调查管道错误

当 PowerShell 无法将管道对象与接收 cmdlet 的参数相关联时,命令将失败。

在以下示例中,我们尝试将注册表项从一个注册表项移动到另一个注册表项。 cmdlet Get-Item 获取目标路径,然后通过管道将它传递给 Move-ItemProperty cmdlet。 命令 Move-ItemProperty 指定要移动的注册表项的当前路径和名称。

Get-Item -Path HKLM:\Software\MyCompany\sales |
Move-ItemProperty -Path HKLM:\Software\MyCompany\design -Name product

命令失败,PowerShell 显示以下错误消息:

Move-ItemProperty : The input object can't be bound to any parameters for
the command either because the command doesn't take pipeline input or the
input and its properties do not match any of the parameters that take
pipeline input.
At line:1 char:23
+ $a | Move-ItemProperty <<<<  -Path HKLM:\Software\MyCompany\design -Name p

若要进行调查,请使用 Trace-Command cmdlet 跟踪 PowerShell 的参数绑定组件。 以下示例在执行管道时跟踪参数绑定。 PSHost 参数在控制台中显示跟踪结果,FilePath 参数将跟踪结果发送到文件供debug.txt以后参考。

Trace-Command -Name ParameterBinding -PSHost -FilePath debug.txt -Expression {
  Get-Item -Path HKLM:\Software\MyCompany\sales |
    Move-ItemProperty -Path HKLM:\Software\MyCompany\design -Name product
}

跟踪的结果很长,但它们显示绑定到 Get-Item cmdlet 的值,然后显示绑定到 cmdlet 的 Move-ItemProperty 命名值。

...
BIND NAMED cmd line args [`Move-ItemProperty`]
BIND arg [HKLM:\Software\MyCompany\design] to parameter [Path]
...
BIND arg [product] to parameter [Name]
...
BIND POSITIONAL cmd line args [`Move-ItemProperty`]
...

最后,它显示尝试将路径绑定到 Move-ItemProperty Destination 参数失败。

...
BIND PIPELINE object to parameters: [`Move-ItemProperty`]
PIPELINE object TYPE = [Microsoft.Win32.RegistryKey]
RESTORING pipeline parameter's original values
Parameter [Destination] PIPELINE INPUT ValueFromPipelineByPropertyName NO
COERCION
Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName NO
COERCION
...

使用 Get-Help cmdlet 查看 Destination 参数的属性。

Get-Help Move-ItemProperty -Parameter Destination

-Destination <String>
    Specifies the path to the destination location.

    Required?                    true
    Position?                    1
    Default value                None
    Accept pipeline input?       True (ByPropertyName)
    Accept wildcard characters?  false

结果显示 ,目标 仅“按属性名称”获取管道输入。 因此,通过管道传输的对象必须具有名为 Destination 的属性。

使用 Get-Member 查看来自 Get-Item的 对象的属性。

Get-Item -Path HKLM:\Software\MyCompany\sales | Get-Member

输出显示该项是没有 Destination 属性的 Microsoft.Win32.RegistryKey 对象。 这解释了命令失败的原因。

Path 参数按名称或值接受管道输入。

Get-Help Move-ItemProperty -Parameter Path

-Path <String[]>
    Specifies the path to the current location of the property. Wildcard
    characters are permitted.

    Required?                    true
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByPropertyName, ByValue)
    Accept wildcard characters?  true

若要修复该命令,必须在 cmdlet 中 Move-ItemProperty 指定目标,并使用 Get-Item 获取要移动的项 的路径

例如,

Get-Item -Path HKLM:\Software\MyCompany\design |
Move-ItemProperty -Destination HKLM:\Software\MyCompany\sales -Name product

内部行延续

如前所述,管道是由管道操作员 (|) 连接的一系列命令,通常写在一行上。 但是,为了便于阅读,PowerShell 允许跨多行拆分管道。 当管道运算符是行上的最后一个标记时,PowerShell 分析程序会将下一行联接到当前命令,以继续构造管道。

例如,以下单行管道:

Command-1 | Command-2 | Command-3

可以编写为:

Command-1 |
    Command-2 |
    Command-3

后续行中的前导空格并不重要。 缩进增强了可读性。

PowerShell 7 添加了对在行开头具有管道字符的管道延续的支持。 以下示例演示如何使用此新功能。

# Wrapping with a pipe at the beginning of a line (no backtick required)
Get-Process | Where-Object CPU | Where-Object Path
    | Get-Item | Where-Object FullName -match "AppData"
    | Sort-Object FullName -Unique

# Wrapping with a pipe on a line by itself
Get-Process | Where-Object CPU | Where-Object Path
    |
    Get-Item | Where-Object FullName -match "AppData"
    |
    Sort-Object FullName -Unique

重要

在 shell 中以交互方式工作时,仅在使用 Ctrl+V 进行粘贴时,才使用管道在行的开头粘贴代码。 右键单击粘贴操作,一次插入一行。 由于行不以管道字符结尾,因此 PowerShell 会将输入视为完整,并按输入执行该行。

另请参阅