【问题标题】:Split a String and then assign the splits拆分字符串,然后分配拆分
【发布时间】:2019-01-13 12:16:32
【问题描述】:

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

汤姆哈迪

布拉德·皮特

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

$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

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

【问题讨论】:

    标签: powershell foreach enumeration


    【解决方案1】:
    # 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's answer


    至于你尝试了什么

    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 也对恰好是集合的输入执行枚举

      • switch 等效于 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-Object cmdlet,否则它指的是foreach 循环(表达式模式) - 参见@987654327 @了解详情。


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

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

    • 性能(执行速度)
      • foreach 循环通常最快,其次是.ForEach() 方法,ForEach-Object cmdlet 最慢(管道通常很慢;见底部)
    • 内存效率
      • 只有ForEach-Object cmdlet(管道)提供流式处理处理,其中每个对象在生成时进行处理;除非将整体结果收集在内存中(例如,与输出到文件相反),否则这保持内存使用不变,无论最终处理了多少对象。
      • foreach.ForEach() 要求输入集合完整存在于内存中。
    • 输出时机
      • ForEach-Object cmdlet(以及一般的管道)在对象被处理/生成时传递对象,因此您通常会立即开始看到输出。
      • foreach.ForEach(),当直接对命令进行操作时,必须先完整地收集该命令的输出,然后才能开始枚举。
    • 语法便利功能集
      • .ForEach() 方法可以按原样用作表达式的一部分,而使用ForEach-Object 需要包含(...),而使用foreach 循环需要包含@987654389 @
      • .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 VM 的结果,带有 Windows PowerShell v5.1 / PowerShell Core 7.2.0-preview.6;请注意,绝对数字并不重要,并且会根据许多变量而有所不同,但 ratio(列 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 以获得出色的分析,从而得出 GitHub feature request #10982
    • foreach 的性能优势随着迭代次数的增加而增长,并且在大数字时相当可观。然而,与迭代次数无关,.ForEach() 的速度似乎是 ForEach-Object 的两倍

    • 就绝对时间而言,PowerShell Core 的性能似乎优于具有大量迭代次数的 Windows PowerShell。

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

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


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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 您能解释一下每个 { } 之前和之后的 ( 和 ) 的必要性以及 @ 符号的作用吗?
      • @() 是一个 array 或对象集合。哈希表@{} 用于创建单个[PSCustomObject],其属性来自哈希表。哈希表是 PowerShell AFAIK 中最快的对象。
      【解决方案3】:

      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}
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-27
        • 2016-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多