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

Split a String and then assign the splits

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

Tom Hardy

Brad Pitt

我用它来从文件中提取名称并拆分它们。

$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

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

Split 给你一个数组,在它拆分的每个位置之后都有内容。 您可以解决数组中的每个条目,然后用于进一步的目的:

例如:("Tom Hardy" -split " ")[0] = Tom

$Names = gc C:\Temp\Name.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所说,其实可以跳过一步,直接从split中保存到两个变量中:

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

foreach ($name in $Names)
{
  $firstname, $lastname = $name -split " "
  #Do whatever you need to do with the names here
}

或作为单行者:

Get-Content -Path "C:\Temp\Name.txt" | % {$firstname, $lastname = $_ -split " "; #Do something with the variables}

与@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'
# 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
}

如果您想收集名称对供以后使用,请考虑将它们包装在自定义对象中,如


至于你试过的:

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 loop statement:

    • 旨在枚举一个指定的内存集合
    • 通过一个自己选择的迭代变量(最好不要选择$_,以免混淆)
    • 例如,foreach ($num in 1, 2, 3) { "number: $num" }
    • 注意:$null 因为输入集合 而不是 导致进入循环体 - 不像 ForEach-Object.
  • PSv4+ 还提供 .ForEach() array method:

    • 类似于foreach循环,它被设计为枚举一个内存中的集合,但是你invoke it as a method 在集合本身上。
    • 类似于ForEach-Object cmdlet,它是自动变量$_反映当前迭代的对象。
    • 提供额外的功能,例如按名称枚举属性、执行类型转换、调用方法。
    • 例如,(1, 2, 3).ForEach({ "number: $_" }
    • 注意:$null 因为输入集合 而不是 导致调用脚本块 - 与 ForEach-Object 不同。
  • 也许令人惊讶的是,PowerShell 的 switch statement 也对碰巧是集合的输入执行枚举

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

有点令人困惑,foreach也是ForEach-Object的内置别名如果你使用foreach,它是决定使用哪个构造的解析上下文:在管道(命令上下文,参数模式)中,foreach 指的是 ForEach-Object cmdlet,否则它指的是 foreach loop(表达式模式)- 详见


权衡:什么时候使用什么构造:

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

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

性能比较:

运行 以下 性能比较命令 ,它使用了一个简单的循环体,显示 foreach.ForEach() 快正如预期的那样,我的测试中 ForEach-Object 最慢。请注意,代码从 this 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-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 是迄今为止最慢的

    • 注意:虽然这是预期的,但考虑到管道引入了开销,减速在很大程度上归因于低效的实现 ForEach-Object(和 Where-Object)从 PowerShell 7.2 开始的 cmdlet - 请参阅 this blog post for an excellent analysis, which led to GitHub feature request #10982.
  • foreach的性能优势随着迭代次数的增加而增长,与大数相当可观。然而,与迭代次数无关,.ForEach() 似乎是 ForEach-Object.

    的两倍左右
  • 就绝对计时而言,PowerShell Core 似乎比 Windows 具有较大迭代次数的 PowerShell 表现更好。

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

  • Re-运行 同一 PowerShell 会话中的测试扩大了性能差距,表明只有 foreach 语句受益于按需编译。


[1] 例如,对比使用 cmdlet Get-Content vs. with language statement switch -File; or contrasting filtering a collection with cmdlet Where-Object vs. via method .Where() or operator -match.

读取文件