我有一个文本文件,在文本文件中有两个名字,就像这样。
Tom Hardy
Brad Pitt
我使用它,从文件中提取名称并将其拆分。
$Names = gc C:TempName.txt
ForEach-Object {-Split $Names}
然后如何将每个名字分配给$FirstName,将每个姓氏分配给$LastName?
这背后的想法是,接下来,对于每个$FirstName,我将使用每个名称创建一个特定的单独项目。
我知道,在我运行以上内容后,名称的每个部分都被分配给$_,所以我可以对每个部分做同样的事情,即
$Names = gc C:TempName.txt
$SplitNames = ForEach-Object {-Split $Names}
ForEach ($_ in $SplitNames) {Write-Host 'Name is' $_}
Name is Tom
Name is Hardy
Name is Brad
Name is Pitt
希望这是有道理的,如果需要更多的澄清,请告诉我。
# Read the input file line by line with Get-Content and send each line
# to the ForEach-Object cmdlet, which sees each line as automatic variable
# $_
Get-Content C:TempName.txt | ForEach-Object {
# Split the line into tokens by whitespace.
# * $firstName receives the 1st token,
# * $lastName the 2nd one (if there were more, $lastName would become an *array*)
$firstName, $lastName = -split $_
# Work with $firstName and $lastName
}
如果您想收集名称对供以后使用,请考虑将它们包装在自定义对象中,如DarkLite1的答案所示。
至于您尝试了什么:
ForEach-Object { -Split $Names }
ForEach ($_ in $SplitNames) {Write-Host 'Name is' $_}
如果在不提供管道输入的情况下调用ForEach-Object
,则脚本块将执行一次,因此ForEach-Object { -Split $Names }
实际上与仅调用-Split $Names
相同。
一般来说,这些语句表明PowerShell的各种枚举结构之间存在混淆。
PowerShell的各种枚举构造:
ForEach-Object
cmdlet:- 设计用于通过管道接收输入(
|
) - 反映自动变量
$_
中的每个输入对象 - 例如
1, 2, 3 | ForEach-Object { "number: $_ " }
- 注意:将
$null
作为输入发送会导致调用,这与foreach
循环不同
- 设计用于通过管道接收输入(
foreach
循环语句:- 被设计为枚举中指定的内存集合
- 通过自行选择的迭代变量(最好不要选择
$_
,以免混淆) - 例如
foreach ($num in 1, 2, 3) { "number: $num" }
- 注意:与
ForEach-Object
不同,作为输入集合的$null
不会导致进入循环体
PSv4+还提供
.ForEach()
阵列方法:- 与
foreach
循环类似,它被设计为枚举内存中的集合,但您将其作为集合本身的方法调用 - 与
ForEach-Object
cmdlet类似,反映当前迭代的对象的是自动变量$_
- 它提供了其他功能,如按名称枚举属性、执行类型转换、调用方法
- 例如
(1, 2, 3).ForEach({ "number: $_" }
- 注意:与
ForEach-Object
不同,作为输入集合的$null
不会导致脚本块的调用
- 与
也许令人惊讶的是,PowerShell的
switch
语句也对恰好是集合的输入执行枚举。switch
等效于foreach ($num in 1, 2, 3) { "number: $num" }
(注意使用自动变量$_
作为隐式迭代器变量):switch (1, 2, 3) { default { "number: $_"; continue } }
switch
在内存效率、性能和输出时序方面与foreach
循环语句相似,因此下面不再单独讨论。它的优点是能够使用复杂的条件,并且能够使用-File
选项直接枚举文件的行- 注意:与
foreach
循环不同,$null
作为输入集合确实会导致对分支的求值
令人困惑的是,foreach
也是ForEach-Object
的内置别名如果使用foreach
,则由解析上下文决定使用哪个构造:在管道(命令上下文、参数模式)中,foreach
指ForEach-Object
cmdlet,否则指foreach
循环(表达式模式)-有关详细信息,请参阅此答案。
权衡:在以下情况下使用什么结构:
注意:以下内容侧重于类似循环的构造,但通常将应用于,一方面是在管道(流)中使用cmdlet,另一方面是语言语句和基于运算符的表达式/方法调用[1]
- 性能(执行速度)
foreach
循环通常最快,其次是.ForEach()
方法,ForEach-Object
cmdlet最慢(管道通常很慢;请参阅底部部分)
- 内存效率
- 只有
ForEach-Object
cmdlet(管道)提供流处理,其中每个对象都在生成时进行处理;除非在内存中收集总体结果(例如,与输出到文件相反),否则无论最终处理多少对象,都会保持内存使用恒定 - CCD_ 44和CCD_
- 只有
- 输出定时
ForEach-Object
cmdlet(以及通常的管道)在处理/生成对象时传递对象,因此您通常会立即看到输出foreach
和.ForEach()
在直接对命令进行操作时,必须提前收集该命令的完整输出,然后才能开始枚举
- 语法便利性和特征集
.ForEach()
方法可以按原样用作表达式的一部分,而使用ForEach-Object
需要封闭(...)
,使用foreach
循环需要封闭$(...)
.ForEach()
方法提供了允许简洁表达式的附加功能(可以用foreach
和ForEach-Object
模拟功能,但更详细)
性能比较:
运行下面的性能比较命令,这些命令使用了一个琐碎的循环体,在我的测试中显示foreach
比.ForEach()
快,ForEach-Object
最慢,正如预期的那样。注意,代码从这个Gist下载并定义了Time-Command
函数(我可以向您保证这样做是安全的,但您应该始终自己检查源代码):
# Download and define the Time-Command function.
irm https://gist.github.com/mklement0/9e1f13978620b09ab2d15da5535d1b27/raw/Time-Command.ps1 | iex
Write-Verbose -vb "1,000 items, average of 10 runs"
$a=1..1000; Time-Command -Count 10 { foreach($e in $a) { (++$e) } }, { $a.ForEach({ (++$_) }) }, { $a | ForEach-Object { (++$_) } } | Out-Host
Write-Verbose -vb "100,000 items, average of 10 runs"
$a=1..1e5; Time-Command -Count 10 { foreach($e in $a) { (++$e) } }, { $a.ForEach({ (++$_) }) }, { $a | ForEach-Object { (++$_) } } | Out-Host
结果来自双核Windows 10虚拟机,带有Windows PowerShell v5.1/PowerShell core 7.2.0-preview.6;请注意,绝对数并不重要,会根据许多变量而变化,但比率(列Factor
)应该会给你一种感觉。
Windows PowerShell v5.1:
VERBOSE: 1,000 items, average of 10 runs
Factor Secs (10-run avg.) Command TimeSpan
------ ------------------ ------- --------
1.00 0.001 foreach($e in $a) { (++$e) } 00:00:00.0013639
3.99 0.005 $a.ForEach({ (++$_) }) 00:00:00.0054464
7.46 0.010 $a | ForEach-Object { (++$_) } 00:00:00.0101785
VERBOSE: 100,000 items, average of 10 runs
Factor Secs (10-run avg.) Command TimeSpan
------ ------------------ ------- --------
1.00 0.014 foreach($e in $a) { (++$e) } 00:00:00.0144434
37.56 0.542 $a.ForEach({ (++$_) }) 00:00:00.5424872
62.61 0.904 $a | ForEach-Object { (++$_) } 00:00:00.9043278
PowerShell Core 7.2.0-预览。6:
VERBOSE: 1,000 items, average of 10 runs
Factor Secs (10-run avg.) Command TimeSpan
------ ------------------ ------- --------
1.00 0.001 foreach($e in $a) { (++$e) } 00:00:00.0013071
4.23 0.006 $a.ForEach({ (++$_) }) 00:00:00.0055324
8.04 0.011 $a | ForEach-Object { (++$_) } 00:00:00.0105058
VERBOSE: 100,000 items, average of 10 runs
Factor Secs (10-run avg.) Command TimeSpan
------ ------------------ ------- --------
1.00 0.010 foreach($e in $a) { (++$e) } 00:00:00.0095252
34.12 0.325 $a.ForEach({ (++$_) }) 00:00:00.3250133
57.83 0.551 $a | ForEach-Object { (++$_) } 00:00:00.5508560
注意:在发现原始方法中的缺陷后,已更新上述基准。新的数字得出了不同的结论
一般观察:
foreach
是迄今为止最快的,其次是.ForEach()
,其中ForEach-Object
是迄今为止最低的- 注意:虽然这是意料之中的事,但考虑到管道引入了开销,速度放缓在很大程度上是由于自PowerShell 7.2起
ForEach-Object
(和Where-Object
)cmdlet的实现效率低下-请参阅此博客文章以获得出色的分析,这导致了GitHub功能请求#10982
- 注意:虽然这是意料之中的事,但考虑到管道引入了开销,速度放缓在很大程度上是由于自PowerShell 7.2起
foreach
的性能优势随着迭代次数的增加而增加,并且在迭代次数较多的情况下是相当大的。然而,与迭代次数无关,.ForEach()
的速度似乎是ForEach-Object
的两倍。就绝对时间而言,PowerShell Core似乎比迭代次数较多的Windows PowerShell性能更好。
在macOS上(结果未在上面显示),减速因素似乎更大,执行速度似乎也更慢。
在同一PowerShell会话中重新运行测试会扩大性能差距,这表明只有
foreach
语句从按需编译中受益。
[1]例如,对比使用cmdletGet-Content
读取文件与使用语言语句switch
-File
读取文件;或者将使用cmdletWhere-Object
过滤集合与通过方法.Where()
或运算符-match
过滤集合进行对比
与@Paxz相同,但有一些解释和建议:
$Names = @(
'Brad Pitt',
'Tom Hardy',
'Daniel Craig Junior'
)
# the .ForEAch method is used as it's faster then piping data to Foreach-Object
$result = $Names.ForEach({
# we use the second argument of -Split to indicate
# we're only interested in two values
$tmpSplit = $_ -split ' ', 2
# we then create an object that allows us to
# name things propertly so we can play with it later withoout hassle
[PSCustomObject]@{
Input = $_
FirstName = $tmpSplit[0]
LastName = $tmpSplit[1]
}
})
# here we show the result of all our objects created
$result
# enable verbose text to he displayed
$VerbosePreference = 'Continue'
$result.ForEach({
# here we can easily address the object by its property names
Write-Verbose "Input '$($_.Input)' FirstName '$($_.FirstName)' LastName '$($_.LastName)'"
})
# disable verbose messages, because we don't need this in production
$VerbosePreference = 'SilentlyContinue'
Split
为您提供了一个数组,其中包含每个拆分后的内容。您可以寻址数组中的每个条目,然后用于进一步的目的:
例如:("Tom Hardy" -split " ")[0]
=Tom
$Names = gc C:TempName.txt
foreach ($name in $Names)
{
$cutted = $name.Split()
$firstname = $cutted[0]
$lastname = $cutted[1]
#Do whatever you need to do with the names here
}
正如@iRon所提到的,您实际上可以跳过一步,直接将其从拆分中保存到两个变量:
$Names = gc C:TempName.txt
foreach ($name in $Names)
{
$firstname, $lastname = $name -split " "
#Do whatever you need to do with the names here
}
或者作为一个oneliner:
Get-Content -Path "C:TempName.txt" | % {$firstname, $lastname = $_ -split " "; #Do something with the variables}