拆分字符串,然后分配拆分



我有一个文本文件,在文本文件中有两个名字,就像这样。

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-Objectcmdlet:

    • 设计用于通过管道接收输入(|)
    • 反映自动变量$_中的每个输入对象
    • 例如1, 2, 3 | ForEach-Object { "number: $_ " }
    • 注意:将$null作为输入发送会导致调用,这与foreach循环不同
  • foreach循环语句:

    • 被设计为枚举中指定的内存集合
    • 通过自行选择的迭代变量(最好不要选择$_,以免混淆)
    • 例如foreach ($num in 1, 2, 3) { "number: $num" }
    • 注意:与ForEach-Object不同,作为输入集合的$null不会导致进入循环体
  • PSv4+还提供.ForEach()阵列方法

    • foreach循环类似,它被设计为枚举内存中的集合,但您将其作为集合本身的方法调用
    • ForEach-Objectcmdlet类似,反映当前迭代的对象的是自动变量$_
    • 它提供了其他功能,如按名称枚举属性、执行类型转换、调用方法
    • 例如(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,则由解析上下文决定使用哪个构造:在管道(命令上下文、参数模式)中,foreachForEach-Objectcmdlet,否则指foreach循环(表达式模式)-有关详细信息,请参阅此答案。


权衡:在以下情况下使用什么结构:

注意:以下内容侧重于类似循环的构造,但通常应用于,一方面是在管道(流)中使用cmdlet,另一方面是语言语句和基于运算符的表达式/方法调用[1]

  • 性能(执行速度)
    • foreach循环通常最快,其次是.ForEach()方法,ForEach-Objectcmdlet最慢(管道通常很慢;请参阅底部部分)
  • 内存效率
    • 只有ForEach-Objectcmdlet(管道)提供处理,其中每个对象都在生成时进行处理;除非在内存中收集总体结果(例如,与输出到文件相反),否则无论最终处理多少对象,都会保持内存使用恒定
    • CCD_ 44和CCD_
  • 输出定时
    • ForEach-Objectcmdlet(以及通常的管道)在处理/生成对象时传递对象,因此您通常会立即看到输出
    • foreach.ForEach()在直接对命令进行操作时,必须提前收集该命令的完整输出,然后才能开始枚举
  • 语法便利性特征集
    • .ForEach()方法可以按原样用作表达式的一部分,而使用ForEach-Object需要封闭(...),使用foreach循环需要封闭$(...)
    • .ForEach()方法提供了允许简洁表达式的附加功能(可以用foreachForEach-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
  • 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}

最新更新