首页 > 解决方案 > 拆分字符串,然后分配拆分

问题描述

我有一个文本文件,在文本文件中有两个名字,一模一样。

汤姆哈迪

布拉德·皮特

我使用它从文件中获取名称并拆分它们。

$Names = gc C:\Temp\Name.txt

ForEach-Object {-Split $Names}

然后如何将每个名字分配给 $FirstName 并将每个姓氏分配给 $LastName?

这背后的想法是,更进一步,对于每个 $FirstName,我将为每个名称创建一个特定的单个项目。

我明白,在我运行上述内容之后,名称的每个部分都分配给 $_ 所以我可以对每个部分做同样的事情,即

$Names = gc C:\Temp\Name.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

希望这是有道理的,如果需要更多说明,请告诉我。

标签: powershellforeachenumeration

解决方案


# 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:\Temp\Name.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" }
    • 注意:$null由于输入集合不会导致进入循环体 - 与ForEach-Object.
  • PSv4+还提供.ForEach()数组方法

    • foreach循环类似,它旨在枚举内存中的集合,但您将其作为集合本身的方法调用。
    • ForEach-Objectcmdlet 类似,它是反映当前迭代对象的自动变量。$_
    • 提供了附加功能,例如按名称枚举属性、执行类型转换、调用方法。
    • 例如,(1, 2, 3).ForEach({ "number: $_" }
    • 注意:$null由于输入集合不会导致脚本块的调用 - 与ForEach-Object.
  • 也许令人惊讶的是,PowerShell 的switch语句也对碰巧是集合的输入执行枚举。

    • switch等价于is(注意使用foreach ($num in 1, 2, 3) { "number: $num" }自动变量$_作为隐式迭代器变量):
      switch (1, 2, 3) { default { "number: $_"; continue } }
    • switch在内存效率、性能和输出时序方面与循环语句类似foreach,所以下面不再单独讨论。它的优点是能够使用复杂的条件以及能够通过-File选项直接枚举文件的行。
    • 注意:$null由于输入集合确实会导致对分支的评估 - 与foreach循环不同。

有点令人困惑的是,foreach也是. ForEach-Object如果使用foreach,则由解析上下文决定使用哪个构造:在管道中(命令上下文,参数模式),foreach指的是ForEach-Objectcmdlet,否则它指的是foreach 循环(表达式模式) - 有关详细信息,请参阅此答案


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

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

  • 性能(执行速度)
    • foreach循环通常最快,其次是方法.ForEach()ForEach-Objectcmdlet 最慢(管道通常很慢;见底部)
  • 记忆效率
    • 只有ForEach-Objectcmdlet(管道)提供处理,其中每个对象在生成时进行处理;除非将整体结果收集在内存中(例如,与输出到文件相反),否则无论最终处理多少对象,这都会使内存使用保持不变。
    • foreach.ForEach()要求输入集合完全存在于内存
  • 输出时序
    • cmdlet(以及一般的ForEach-Object管道)在处理/生成对象时传递对象,因此您通常会立即开始看到输出。
    • foreach并且.ForEach(),当直接对命令进行操作时,必须先完整地收集该命令的输出,然后才能开始枚举。
  • 语法便利功能集
    • .ForEach()方法可以按原样用作表达式的一部分,而使用ForEach-Object需要封闭(...),使用foreach循环需要封闭$(...)
    • .ForEach()方法提供了允许简洁表达式的附加功能(使用foreachand模拟功能ForEach-Object是可能的,但更冗长)

性能对比:

运行以下使用简单循环体的性能比较命令foreach显示比.ForEach()我的测试更快,并且ForEach-Object最慢,正如预期的那样。请注意,代码从这个 GistTime-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 VM 的结果,带有 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 核心 7.2.0-preview.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(and ) cmdlet 的低效实现ForEach-ObjectWhere-Object- 请参阅此博客文章以获得出色的分析,这导致了GitHub功能请求 #10982
  • 的性能优势foreach随着迭代次数的增加而增长,并且在大量的情况下是相当可观的。然而,与迭代次数无关,.ForEach()它的速度似乎大约是 的两倍ForEach-Object

  • 就绝对时间而言,PowerShell Core 似乎比具有大量迭代次数的 Windows PowerShell 执行得更好。

  • 在 macOS 上(结果未在上面显示),减速因素似乎更大,并且绝对执行似乎也更慢。

  • 在同一个 PowerShell 会话中重新运行测试会扩大性能差距,这表明只有foreach语句才能从按需编译中受益。


[1] 例如,对比使用cmdlet Get-Content与使用语言语句 读取文件;或对比使用cmdlet与通过方法运算符过滤集合。switch -File Where-Object .Where() -match


推荐阅读