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。