PowerShell笔记 - 4.管道

本系列是一个重新学习PowerShell的笔记,内容引用自PowerShell中文博客

使用管道

排序导并导出JSON格式文件

PS C:\Code> ls | Sort-Object -Descending Name | Select-Object -First 1 Name,Length,LastWriteTime | ConvertTo-Json | Out-File ls.txt                                                                                                             PS C:\Code> Get-Content .\ls.txt
{
    "Name":  "Redis",
    "Length":  null,
    "LastWriteTime":  "\/Date(1623377222092)\/"
}
首先列出当前目录下的目录和文件,然后根据文件名降序排列,再投影文件名,文件大小,文件的修改时间,转换成Html格式,输出到当前目录的ls.txt

面向对象的管道

上面的例子属于面向对象的管道,每个命令的末尾可以使用新的命令对上个命令的结果做进一步处理,除非管道是以输出命令结束的。就像Sort-Object一样,对文件的列表进行排序,需要告诉它排序的关键字,按照升序还是降序。ls的返回值为一个数组,数组中的每一个元素都是一个对象,对象的每一个属性都可以作为Sort-Object的排序关键字。但是排序时必须指定一个具体的关键字,因为Powershell所传递的对象可能有很多属性。不像普通的文本,对象的信息都是结构化的,因此也使得Powershell的管道变得更加强大和方便。

转换命令执行的结果为文本

在执行Powershell命令时,解释器会默认在命令的结尾追加一个管道命令,Out-Default,这样可以将原来的对象结果以文本的形式显示在控制台上,但是并没有将结果进行转换,所以可以继续使用其它管道对对象的结果进行操作,但是一旦使用了诸如ConvertTo-JSON这样的命令后,就会将结果转换成固定格式的纯文本。

常用管道命令

    #常用的对管道结果进一步处理的命令有:
    Compare-Object: 比较两组对象。
    ConvertTo-Html: 将 Microsoft .NET Framework 对象转换为可在 Web 浏览器中显示的 HTML。
    Export-Clixml: 创建对象的基于 XML 的表示形式并将其存储在文件中。
    Export-Csv: 将 Microsoft .NET Framework 对象转换为一系列以逗号分隔的、长度可变的 (CSV) 字符串,并将这些字符串保存到一个 CSV 文件中。
    ForEach-Object: 针对每一组输入对象执行操作。
    Format-List: 将输出的格式设置为属性列表,其中每个属性均各占一行显示。
    Format-Table: 将输出的格式设置为表。
    Format-Wide: 将对象的格式设置为只能显示每个对象的一个属性的宽表。
    Get-Unique: 从排序列表返回唯一项目。
    Group-Object: 指定的属性包含相同值的组对象。
    Import-Clixml: 导入 CLIXML 文件,并在 Windows PowerShell 中创建相应的对象。
    Measure-Object: 计算对象的数字属性以及字符串对象(如文本文件)中的字符数、单词数和行数。
    more: 对结果分屏显示。
    Out-File: 将输出发送到文件。
    Out-Null: 删除输出,不将其发送到控制台。
    Out-Printer: 将输出发送到打印机。
    Out-String: 将对象作为一列字符串发送到主机。
    Select-Object: 选择一个对象或一组对象的指定属性。它还可以从对象的数组中选择唯一对象,也可以从对象数组的开头或末尾选择指定个数的对象。
    Sort-Object: 按属性值对象进行排序。
    Tee-Object: 将命令输出保存在文件或变量中,并将其显示在控制台中。
    Where-Object: 创建控制哪些对象沿着命令管道传递的筛选器。

管道的处理模式

当我们把许多命名组合成一个管道时,可能会感兴趣每一个命令的执行时是顺序执行还是同时执行?通过管道处理结果实际上是实时的。这就是为什么存在两个管道模式:

顺序模式(较慢)
在顺序模式中管道中同一时间只执行一条命令,只有当前一条命令的所有执行完毕,才会把所有结果交付给下一条 命令。这种模式速度慢并且耗内存,因为必须需要很多次分配空间存储中间结果。
流模式(较快)
流模式会立即执行所有命令,同一时间可能在执行多条命令。前一条命令可能会产生多个结果,但是一旦产生其中一个结果,就会立即交付给下一条命令处理。这样的流模式节省比较节省内存,可能管道的某个任务还在执行,但是已经有部分结果输出了。减少了中间结果的保存。

管道命令的阻塞

可以使用Sort-Object对管道的结果进行排序,但是有时候排序可能导致整个操作系统阻塞,因为排序命令的的执行属于顺序模式,必须得上一条命令的结果全部完成,才能排序。
因此在使用这类命令时,要注意操作对象的大小,和它们需要的内存。例如这条命令:
Dir C: -recurse | Sort-Object -recurse 选项是递归查询子目录,可想而知系统盘的文件和目录有多大。
这条命令一旦运行起来,需要等很长很长的时间,甚至可能导致系统崩溃,得重启电脑。
你可以在执行这条命令时,打开任务管理器查看Powershell进程的内存占用在以每秒种几十兆的速率增加。
到底哪些命令可能系统阻塞,要视命令的实现方式以及处理的对象大小决定,例如Sort-object导致阻塞的原因肯定是由于技术实现上采用的是内排序,没有使用外排序。
但是象Out-Host -paging 这样的命令属于流出来模式,就一般不会导致系统阻塞。

将对象转换为文本

怎样将Powershell的对象结果转换成文本并显示在控制台上。Powershell已经内置Out-Default命令追加在管道的命令串的末尾。因此你使用dir 和dir | out-default的结果是相同的。
Out-Default可以将对象转换成可视的文本。事实上Out-Default会首先调用Format-Table,将更多的属性默认隐藏。再调用Out-Host将结果输出在控制台上。因此下面的四组命令执行结果是相同的。
PS C:\Code> ls                                                                                                  PS C:\Code> ls | Format-Table                                                                                   PS C:\Code> ls | Format-Table | Out-Default                                                                     PS C:\Code> ls | Format-Table | Out-Default |Out-Host  

#显示隐藏的对象属性
PS C:\Code\testdir> ls | Format-Table *

#使用通配符显示进程的名字和其它以”pe”打头,以”64″结尾的进程。
PS C:\Code> Get-Process i* | Format-Table Name,pe*64                                                                                                                                         
Name                                PeakPagedMemorySize64 PeakWorkingSet64 PeakVirtualMemorySize64
----                                --------------------- ---------------- -----------------------
ibmpmsvc                                          2994176          9728000              4404154368
Idle                                                61440            12288                    8192
igfxCUIService                                    3088384         12537856           2203426746368

#添加定义列
PS C:\Code> ls | Format-Table Name,{ [int]($_.Length/1kb) }                                                                                                                                  
Name                   [int]($_.Length/1kb)
----                  ----------------------
0605                                       0
0707                                       0
0811                                       0
0917                                       0
1020                                       0
1201                                       0

#定义列头
PS C:\Code> $column = @{Expression={ [int]($_.Length/1KB) }; Label="KB" }                                                                                                                    PS C:\Code> ls | Format-Table Name,$column                                                                                                                                                   
Name                  KB
----                  --
0605                   0
0707                   0
0811                   0
0917                   0

排序及分组

#分组
PS C:\Code> ls | Group-Object Extension |Format-Table Name,Count                                                                                                                             
Name  Count
----  -----
         18
.xlsx     1
.txt      1

#排序
PS C:\Code> ls | Group-Object Extension |Sort-Object @{expression="Count";Descending=$true},@{expression="Name";Ascending=$true} |Format-Table Name,Count                                    
Name  Count
----  -----
         18
.txt      1
.xlsx     1

分析和对比

使用Measure-Object和Compare-Object可以统计和对比管道结果。
Measure-Object允许指定待统计对象的属性。Compare-Object可以对比对象前后的快照。

统计和计算

#统计
PS C:\Code> ls | Measure-Object Length                                                                                  

Count    : 2 #文件数量
Average  :
Sum      :
Maximum  :
Minimum  :
Property : Length

#指定统计最大最小等
PS C:\Code> ls | Measure-Object Length -Average -Sum -Maximum -Minimum

Count    : 2
Average  : 6566
Sum      : 13132
Maximum  : 12930
Minimum  : 202
Property : Length

#统计文本文件内的行、字、词等
PS C:\Code> Get-Content .\ls.txt | Measure-Object -Line -Word -Character

Lines Words Characters Property
----- ----- ---------- --------
    5     8         90

对比

PS C:\Code> $d1 = Get-Date
PS C:\Code> $d2 = Get-Date
PS C:\Code> Compare-Object $d1 $d2

InputObject        SideIndicator
-----------        -------------
2021/9/10 17:45:50 => #新增项
2021/9/10 17:45:36 <= #删除项

#指定属性对比
PS C:\Code> $h1 = @{N=1}
PS C:\Code> $h2 = @{N=2}
PS C:\Code> Compare-Object $h1 $h2 -Property Values

Values SideIndicator
------ -------------
{2}    =>
{1}    <=

PS C:\Code> Compare-Object $h1 $h2 -Property Keys

扩展类型系统

Powershell一个最吸引人的功能是它能够将任何对象转换成文本,我们已经使用过将对象属性以不同的版式转换成文本,并且输出。更令人惊奇的是Powershell会把最重要最能代表这个对象本质的信息输出。一个对象有很多属性,为什么它单单就输出那几个属性呢?
如下:

PS C:\> dir | Select-Object -First 1 | Format-Table *  -Wrap
PSPath  PSParentPath    PSChildName     PSDrive
------

Powershell会最大限度的输出每个属性,但是这样的输出基本上没有意义,不利于用户阅读。那到底是什么让Powershell默认只显示此属性不显示彼属性呢?
是“扩展类型系统”Extended Type System (ETS),ETS会对管道中对象转换成文本的机制进行宏观调控。
ETS由两部分组成,一部分控制对象的版式,一部分控制对象的属性,今天主要关心第一部分。

文本转换不可逆

在管道中将对象结果转换成文本后,不能再将文本转换成对象,因为ETS不能处理文本。
如果通过ConvertTo-String将目录列表的转换成String后,使用Format-Table和Format-List这些命令就会无效。

PS C:> $test = Dir |Out-String                                                                                                                                                    PS C:> $test                                                                                                                                                                      

    Directory: C:


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2021/6/10     14:40                8001
d-----        2021/6/11     10:12                8002
d-----        2021/6/11     10:12                8003

PS C:> $test | Format-List


    Directory: C:

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2021/6/10     14:40                8001
d-----        2021/6/11     10:12                8002
d-----        2021/6/11     10:12                8003


PS C:> dir | Format-List                                                                                                                                                          

    Directory: C:

Name           : 8001
CreationTime   : 2021/6/10 13:43:27
LastWriteTime  : 2021/6/10 14:40:50
LastAccessTime : 2021/6/10 14:40:50
Mode           : d-----
LinkType       :
Target         : {}

已知对象格式化

如果使用了格式化的命令,但是没有指定具体的属性(如: dir | Format-Table)。ETS将会首次大展拳脚,它会决定那些对象应当显示,那些属性应当被自动选择。ETS在做这些工作之前,首先应当弄清楚,那些对象能够被转换成文本。

PS C:> (Dir)[0].GetType().FullName                                                                                                                                                System.IO.DirectoryInfo

Dir 返回一个System.IO.DirectoryInfo对象,并且包含了这个对象里面的System.IO.FileInfo对象和System.IO.DirectoryInfo子对象。这样ETS就可以去检查自己的内部记录,通过内部记录的配置,将对象转换成文本。这些内部记录为XML文件,扩展名为“.ps1xml”

PS C:> dir $PSHOME *format.ps1*                                                                                                                                                   

    Directory: C:\Windows\System32\WindowsPowerShell\v1.0


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        2019/3/19     12:46          12825 Certificate.format.ps1xml
-a----        2019/3/19     12:46           5074 Diagnostics.Format.ps1xml
-a----        2019/3/19     12:46         138223 DotNetTypes.format.ps1xml
-a----        2019/3/19     12:46          10144 Event.Format.ps1xml
-a----        2019/3/19     12:46          25526 FileSystem.format.ps1xml
-a----        2019/3/19     12:46          91655 Help.format.ps1xml
-a----        2019/3/19     12:46         138625 HelpV3.format.ps1xml
-a----        2019/3/19     12:46         206468 PowerShellCore.format.ps1xml
-a----        2019/3/19     12:46           4097 PowerShellTrace.format.ps1xml
-a----        2019/3/19     12:46           8458 Registry.format.ps1xml
-a----        2019/3/19     12:46          16598 WSMan.Format.ps1xml

每一个对象详细地被定义在这些XML文件中,定义包括那些对象属性支持转换成文本,那些对象应当默认显示在列表或者表格中。
有一点之前说过,对于一行上面的混合命令“ Get-Process ; dir”ETS不支持,要想避免最好的方式是每个命令明确地指定版式。

PS C:> Get-Process | Format-Table ; dir | Format-Table

未知对象格式化

在ps1xml中定义过的对象属于已知对象,那些未知对象ETS应当怎样处理呢?
对于未知对象,ETS遵循一个规律: 如果对象的属性少于5个则表格显示,否则列表显示。
下面的例子创建一个对象,并向对象中逐个增加属性。

PS C:> $obj = New-Object psobject                                                                            PS C:> Add-Member -MemberType NoteProperty -Name "A" -Value "1" -InputObject $obj                            PS C:> $obj                                                                                                  
A
-
1


PS C:> Add-Member -MemberType NoteProperty -Name "B" -Value "2" -InputObject $obj                            PS C:> Add-Member -MemberType NoteProperty -Name "C" -Value "3" -InputObject $obj                            PS C:> $obj                                                                                                  
A B C
- - -
1 2 3


PS C:> Add-Member -MemberType NoteProperty -Name "D" -Value "4" -InputObject $obj                            PS C:> Add-Member -MemberType NoteProperty -Name "E" -Value "5" -InputObject $obj                            PS C:> $obj                                                                                                  

A : 1
B : 2
C : 3
D : 4
E : 5

应急模式

如果ETS从输出中发现临界状态,会自动切换到列表显示。例如“Get-Process; Dir”,ETS正在以表格形式输出Process对象,但是突然碰到一个FileInfo对象,就会直接切换到列表模式,输出其它类型的对象。

隐藏列

如果碰到未知的对象,ETS会试着从管道输出的第一个结果寻找线索,这样可能导致一个奇怪的现象。ETS会根据未知对象的第一个结果,来判断属性,但第一条结果的属性并不总会输出。可能再碰到包含更多属性的对象时,当前选择的属性信息就可能会被抑制。
接下来的例子演示那些信息会被抑制,Get-Process 返回正在运行的所有进程,然后通过StartTime进行排序,最输出每个进程的名称和开启时间:

PS C:> Get-Process | Sort-Object StartTime | Select-Object Name                                              Sort-Object : Exception getting "StartTime": "Access is denied"
At line:1 char:15
+ Get-Process | Sort-Object StartTime | Select-Object Name
+               ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidResult: (System.Diagnostics.Process (1E.Client):PSObject) [Sort-Object], GetValue
   InvocationException
    + FullyQualifiedErrorId : ExpressionEvaluation,Microsoft.PowerShell.Commands.SortObjectCommand

当执行上面的命令行时,会收到许多错误信息。这些错误信息并不是来源于命令,而是可能因为当前控制台没有管理员权限,某些系统进程拒绝访问。输出的进程中可能有一部分进程只有进程名(Name),没有开启时间(StartTime),开启时间被抑制了。
使用Select-Object,会删除对象的某些属性,但是对象本身的属性是不能删除的,所以ETS会在管道中重新生成一个对象,类型为:System.Management.Automation.PSCustomObject。

PS C:> Get-Process | foreach{$_.GetType().FullName} | Select-Object -First 1                                 System.Diagnostics.Process
PS C:> (Get-Process | foreach{$_.GetType().FullName} | Select-Object -First 1 Name).GetType().FullName       System.Management.Automation.PSCustomObject

因为PSCustomObject在ETS配置中没有记录,就会输出全部属性。
管道结果之前根据StartTime升序排列过,所以前面的进程由于权限问题没有StartTime。

posted @ 2021-09-13 12:13  门前有根大呲花  阅读(241)  评论(0编辑  收藏  举报