经原视频作者 ABMedia 授权,本文由 Learn PowerShell 系列教程翻译整理而来。为尽量保证行文流畅和正式,在不改变作者原意的情况下,文字版做了一些删改修饰。引用样式文字皆为译者注,文中部分配图为译者根据原作者语境添设。
我们已经学了不少 PowerShell 的知识,掌握了它的基本使用方法,不过,我们还没有用它解决实际问题。了解完了命令是如何工作的,以及它们的的内部架构,现在,是时候完成一些现实中的案例了。其他命令行上,完成这些任务是一场噩梦。但是在 PowerShell 上却非常轻松。
变量
在我们开始讨论之前,我想谈谈变量。因为变量是一个非常有用的工具,我们将在整个系列视频中大量使用。
首先,变量,本质上是我们可以存储对象的地方。每个变量有一个独一无二的名字,我们用变量名来区分它们。
举个例子,想象一下这种情况:我想获取所有 CPU 时间超过 20 的进程,然后多次处理这些进程。假设我获取了这些进程,然后想求出其平均值;我还想遍历每个进程后再进行一次过滤;我也想对它们进行排序。我需要分别获得这三个结果,它们各有用处。那么,这就需要我们每次都写完全相同的东西。
也就是说,对于第一个任务,首先获取所有进程,过滤,然后求平均值;为了完成第二个任务,要获取所有进程,过滤,再次过滤;第三个任务也要这么做。这么写确实能完成任务,但存在问题。一次又一次地写完全相同的命令不仅需要花费我们更多时间,而且,让计算机去处理这些任务也比实际需要的更慢,因为我们是在让 PowerShell 一遍又一遍地过滤完全一样的进程,我们让它把同样一件事,干了三遍。
如果我们能将事情干一遍后,记住它输出了什么对象,然后反复利用这些相同的对象就好了,这样写更加方便,而且对于计算机 CPU 和内存而言,也更有效率。
事实上,这是能做到的,变量(Variable)正为此而生。变量能记下对象,以便重复使用。我们来打开 PowerShell,看看具体怎么回事。
使用变量的方式是先写一个$
,接着写上一个独一无二的名字。不妨就用上面提到的例子,我要获取所有进程,然后筛选出那些 CPU 大于 20 的对象。现在,我们要做的是将这些对象存储在一个变量里面。首先选一个变量名,就叫它$expensiveProcesses
吧。名字实际上无所谓,它只是方便我们之后使用该变量,只要确保它和其他变量不一样,就不会有问题。
$expensiveProcesses
然后,要将这些对象放入变量中,需要使用=
(赋值符号),这句命令意思是:将后面命令的输出的值赋给$expensiveProcesses
。
$expensiveProcesses = Get-Process | Where { $_.CPU -gt 20}
运行之后,好像什么也没有发生?失败了吗?实际上,并没有。所有对象现在已经放到变量中。因为它们已经在变量中了,PowerShell 选择不显示任何东西,对象已经存储到正确的地方了。
那么,这就是为变量赋值的方法,你能随心所欲地修改变量。
不过,该怎么使用变量呢?该怎么获取变量中的内容并操作它们呢?也很简单:写出变量的名字就可以了,就像这样:
如果我只是简单地写出变量名,不用它做任何其他事情,它会获取其中的所有对象,并且由于我们没有操作这些对象,PowerShell 将它们输出在终端中。
在上述案例中,我们要做的第一件事是获取变量中的内容,并将其 CPU 时间过滤出来,算出平均值。因此,首先获取变量中的内容,然后将其中的数据通过管道传输到 ForEach
中,然后从中计算出平均值。
这就是变量的使用方式。这还解释了我们在第 1 集中没有详述的一个问题:使用ForEach
时,$_
是什么意思?答案很简单,$_
是一个特殊的变量,它会被ForEach
根据管道中的对象,自动替换。它只是一个变量,每有一个对象,就会被ForEach
更换。
$_
还有一种写法:$PSItem
在ForEach
中,我们在这个被换出的变量后面加上一个点,还记得在第 1 集中我们说,这个点意味着我们将访问对象内部的某个东西。那么假设变量person
中含有一个对象,它包含三个属性:Name
、Birthday
和Organisation
。
现在,如果想单独获取这个人的名字,可以先写$person
,然后写 .Name,也就是将人放进这个变量,仅从变量获取人名:
另外,设置/更改变量非常简单,方法是写一个等号,接着写需要存入其中的值,比如说Mike
:
$person.Name = "Mike"
命令就是字面意思:把文件对象的Name
属性值修改为Mike
,就像将某个命令的结果赋值给变量一样,为其赋值后,现在如果我们再看此属性——Name
变成了Mike
,对象的属性值可以随意更改。
这就是关于变量的全部内容了,我们将在剩下的视频中一直使用变量,因而不用担心,你很快就会熟悉它们。
CSV 文件
我们从一个假想的场景开始讲起,现在有一个csv
文件,其中是学校里面 100 名学生的名单,和它们的(假的)数学、英语成绩:
本文 CSV 文件地址
我们需要:从文件中求出学生们英文和数学的平均成绩,用 PowerShell 完成这个任务很容易,下面演示具体步骤。
首先要介绍 PowerShell 中一个非常有用的命令:Import-Csv
,其作用是接收一个 CSV 文件,将其中的所有数据转换为对象。也就是说,我们会获得 100 个对象,每个对象都有Name
、Age
、Maths
以及English
属性。执行一个命令后加载所有数据。对于其他格式,PowerShell 中也有 XML 和 JSON 的等效命令。
我们来为Import-Csv
运行Get-Help
,稍微了解一下它的用法,看这一行,很简单,我们只需要提供一个东西:文件路径:
来试试看,运行Import-Csv
,将Path
设置为 CSV 文件的路径,之后再将把命令的输出放进一个叫为students
的变量中,这样才能轻松地操作这些对象,运行试试:
类型转换 Casting
有了这个变量,现在可以求平均值了,但是,还有一件事要做。
CSV 文件有一个根本性缺陷,它们无法区分数值是文本还是数字。也就是说,读取 CSV 文件时,CSV 没有办法知道是把属性当作文本,还是当作其他类型。对于计算机来说,数字12
和文字12
是有区别的,前者是数字,可以进行数学运算,后者只是只是字母或符号,CSV 无法区分文本和其他类型,这意味着任何读取 CSV 的内容的应用,会将所有属性视为文本。对于其他格式,比如 XML 和 JSON,这不是个问题,因为它们实际上有办法区分属性的类型,但 CSV 格式没有办法,所以每个属性都会被当成字面意义上的符号,视为文本。PowerShell 也不例外,在这里看到的所有属性,它们的值实际上都是文本类型:
大多数时候,这种情况对我们来说不构成麻烦。但是,现在我们要进行一些数学运算,求出平均值,就必须要把值转换为数字类型。
在 PowerShell 中将文本转换为数字很简单,假设有一个名为t
的变量,我将文本 123 放入其中,请注意引号意味着放入的是文本:
$t = "123"
要是想把变量中的内容变成一个数字,很简单,我们像获取变量一样写出变量名,在变量之前输入[int]
:
[int]$t
int
是 integer 的缩写,意思是整数
,这种方法会输出一个数字类型。我们是在获取变量中的内容后,将其变成,或者说,转换
成数字。那么,按 ENTER,就会看到转换过数字。
要是我想将变量t
设置为数字类型,就这么写:
$t = [int]$t
取出t
中的内容,也就是这个123
文本,将其转换为数字,再把这个新数字放回变量中。现在,变量从文本类型变成了数字类型:
这些概念基本上适用于现有的每一种编程语言,是的,你正在从本系列视频中,学到适用于完整编程语言的内容。
那么,下面对获取的学生列表执行这一操作。我们要把Maths
和English
转换为数字,实现起来很简单。输入学生
变量,将它的每一行中的数学
改成转换为整数的数学
,每有一个学生,我们将其数学
属性转换为数字,再把转换后的放回去,英语属性也是同样的道理:
好了,它们现在都变成数字了。现在,要求得数学平均成绩,像之前一样,用ForEach
将其过滤到仅剩数学
属性,再用Measure -Average
来得到平均值,执行命令后,便得到了平均成绩:
我知道有些命令看起来有点长,但是,它拥有无限的灵活性。我们可以用这些数据做任何事情,比如要是有一个设备列表,我们能列出每一台设备上的文件,然后操作这些文件,一通百通,一种思路能用于各种任务。
NoteProperty
不过,虽然这么说,上面的还不够 Cool,我们不妨更进一步,但在这之前,先谈谈属性。
要是你还记得第 1 集,我说过每个对象都是由属性组成的,我说过除属性之外,没有别的了,这种说法不太准确。属性并非对象中的唯一内容,对象里还有其他成分。
这些东西中的大部分都……无足轻重。其实,对象的大部分组成部分都很直白,能顾名思义。比如ScriptProperty
,它会在你获取或者修改属性时,运行一个小脚本,也就是说,它还是一个属性,只是为了更加方便而设计。还有很多顾名思义的东西,我就不费口舌了。但 NoteProperty 这个属性确实需要展开诠释。
属性有一个很重要的特点:假设我们有一个代表进程的对象,它有三个属性。那么,这三个属性是无法更改的。
它们实际上存储在 .NET 代码中,进一步说,你能获取的各种不同类型的对象,进程
对象也好,文件
对象也罢,你看到的所有属性
都是从 .NET 的代码中生成的。它们甚至在我们启动 PowerShell 之前就已经存在了,这同时意味着,我们无法更改他们。那些属性已经在代码中预先定义好了,因而,它们永远保持不变,无法删除也无法添加。
但是,NoteProperty 是一种特殊的属性。我们可以随时向任何对象添加NoteProperty
。NoteProperty
能让我们将任何额外信息附加到某些东西上,所以可以被用来做很多事情。
还记得Import-Csv
输出的的对象吗?这些对象上的所有属性实际上都是 NoteProperties
:
因为Import-Csv
不能在它读取 CSV 文件之前就知道需要什么属性,这显然是在 PowerShell 运行时才发生的,也就是说,它输出的所有对象的每个属性都是 NoteProperty。
我们可以把 NoteProperty 用于我们新任务。
Add-Member
我们已经求得出了分数的平均值,但现在,我们要实际修改 CSV 文件:我们先添加一个全新的Sum
列,其包含每个学生的英语和数学成绩的总和
。做到这一点非常简单,像之前那样,我们要先加载 CSV 文件,然后我们要从该文件中获取所有对象,并将我们自己的Sum
属性添加到每个对象中。之后,每个对象都有一个Sum
属性了。再然后,就要填写所有这些Sum
属性。最后,用名为Export-Csv
的命令,将对象及其所有属性保存回 CSV 中。
那么,我们来试试。
首先,要搞清楚该怎么去给对象添加属性,比如说,在这个 $file 变量中,有一个非常简单的文件对象,是通过运行ls
,再用Where
将其过滤到仅此一个文件得到的:
现在,我要把自己的属性添加到此对象中,我要把这个属性叫做Importance
,用来标识文件的重要性。那么,这就要使用本节要学习的最后一个命令,Add-Member
,它允许用户向对象添加一个小部分。
首先,我们需要准确地告诉它想要给什么对象添加属性,InputObject
参数值就是file
,因为我想将属性添加到这个文件中:
Add-Member -InputObject $file
之后,我们要准确地告诉它我要添加什么样的成员:MemberType
,在此例中,我们希望它是一个NoteProperty
:
Add-Member -InputObject $file -MemberType NoteProperty
现在我们要输入名称,我想称之为Importance
,你会注意到,由于Name
是文本,我把它放在引号中,再说一次,从技术上讲,我可以不用引号,因为如果只有一个单词,PowerShell 确实可以让你使用不带引号的文本,但严格的说,这是不符合规范的:
Add-Member -InputObject $file -MemberType NoteProperty -Name "Importance"
最后,我们要告诉它我们希望设定属性的初始值,使用Value
,就数字0
吧:
Add-Member -InputObject $file -MemberType NoteProperty -Name "Importance" -Value 0
但是,PowerShell 认为我们的属性不够重要,所以不把它显示在表格视图中:
不过也没关系,专门告诉Format-Table
我们希望它也向展示这个属性即可,方案是使用Property
参数,告诉表格我们希望它向我们显示哪些属性。假设我们想看到Name
、Length
,和我们添加的Importance
,就像这样:
当然,在这个案例中,我们确实需要稍微调整一下Format-Table
来向我们显示它,因为命令认为该属性不值得展示,但无论如何我们成功了,能看到这个属性的值是 0。
该如何修改这个值呢:方法也很简单,获取我们的文件变量,进入其中更深一层的Importance
属性,将其设置为5
,完成:
好的,来总结一下,不像常规的预设的属性,NoteProperties 可以自行添加,添加的方式是使用Add-Member
。输入对象;告诉它我们想要一个NoteProperty
,然后告诉它该属性的名称以及放入其中的内容,最后,一旦我们属性添加完成,我们就可以通过变量、一个点和属性值来修改它。
现在,让我们把所有知识都应用到实例。
案例
我想要做的是为每一个 CSV 文件中的对象添加Sum
属性,以此来保存存数学和英语的总和。
为此,需要使用ForEach
。我们先获取所有对象,然后在ForEach
中运行Add-Member
为每个对象添加Sum
属性。InputObject将是
$_,我们希望每次都将属性添加到我们当前所在的对象中。当然,这里还需要
NoteProperty`,而不是其他的能读取的属性。Name 为 「Sum」。
$students | ForEach { Add-Member -InputObject $_ -MemberType NoteProperty -Name "Sum"}
Sum
,之后是Sum
属性的值。此时有两种思路:你可以把它们全部设为 0,然后执行第二个ForEach
,遍历每个对象再将属性设置为正确的值。不过,这样做意义不大,因为它们就在这里,我们可以直接赋值,做法如下:
我们希望其属性值为:当前对象的数学
加上当前对象的英语
,对吧?那么我们如何将两个数字相加呢?退一步讲,假设有一个名为singleStudent
的变量,其中只包含一个学生,该怎么把数学
和英语
分数加在一起?
很简单,首先要获取Maths
的值,因而我们要获取变量,并访问其中的Maths
;然后我们要把它和English
相加,所以我们在变量中获取 English 属性。它们加在一起后,因为我们没有把结果存储到任何地方,PowerShell 直接将它们显示了出来:
因为想把我们当前对象的数学添加到当前对象的英语中,我们需要对 ForEach 中的 $_ 做同样的事情:
$students | ForEach { Add-Member -InputObject $_ -MemberType NoteProperty -Name "Sum" -Value $_.Maths + $_.English}
不过,有一个小问题:当 PowerShell 看到这个表达时,它实际上看到的是运行 Add-Member
,然后获取其输出并将其和English
相加,那……没有任何意义。因此,我们需要明确表示让它一次性完成相加这个指令,把它放进括号就可以了:
$students | ForEach { Add-Member -InputObject $_ -MemberType NoteProperty -Name "Sum" -Value ($_.Maths + $_.English)}
这些括号基本功能是让 PowerShell先做这件事
,然后再使用这个表达式的结果。运行命令后,如果我们查看变量中的内容,是我们预期的结果:
所有的总和
都已填写完毕。如果想把它保存为 CSV 文件,就将其通过管道传输到Export-Csv
中,提供一个文件名,就像这样:
$students | ForEach { Add-Member -InputObject $_ -MemberType NoteProperty -Name "Sum" -Value ($_.Maths + $_.English)} | Export-Csv sum.csv
我知道你可能在想:这也太繁琐了
,但实际上并没有,一开始写起来是有点慢,因为你还在习惯它。这是学习任何东西必经的过程。
那么,本期视频就快结束了。不过,我知道在整个视频中你一直在想一件事,不管你有没有意识到,那就是,「这些命令太长了」。说真的,看看这个,看看这有多长:
有没有更短的书写方法?实际上确实有,我们可以使用一些简单的别名来帮助缩短命令……下一个视频我们再讲这些。
相关链接
作者频道: Discord
联系译者:info.mirtle.org
RSS 订阅本系列文章:RSSHub
本文转自: https://sspai.com/post/72820
本站仅做收录,版权归原作者所有。