【问题标题】:Ignore first and last line in file忽略文件中的第一行和最后一行
【发布时间】:2018-07-05 23:49:41
【问题描述】:

我正在尝试使用 PowerShell 替换多个文本文件的某些列中的字符。我让它工作得很好,除了我需要忽略每个文件中的第一行和最后一行,我无法让它工作。

这是我目前所拥有的:

$Location = "C:\Users\gerhardl\Documents\Tenacity\TEMP\POWERSHELL TESTS"
$Data = "$Location\*.TXT"
$Output = "$Location\Fixed"

Get-Item $Data |
    ForEach-Object {
        $file = $_
        $_ | 
            Get-Content | 
            ForEach-Object {
                $Beginning = $_.Substring(0,105)
                $Account = $_.Substring(105,20) -replace "[ABCDEFGHIJKLMNOPQRSTUVWXYZ]", " "
                $End = $_.Substring(125)
                '{0}{1}{2}' -f $Beginning,$Account,$End
            } |
            Set-Content -Path (Join-Path $Output  $file.Name)

    }

我知道有类似的线程,但我的 For Each 循环似乎不能很好地满足这些建议。

【问题讨论】:

  • 要删除第一行和最后一行还是保持不变?

标签: powershell


【解决方案1】:

您可以使用-Skip 1-SkipLast 1

Get-Content $file  | Select-Object -Skip 1 | Select-Object -SkipLast 1

为 PS

$text = Get-Content $file | Select-Object -Skip 1
$newText = $text.GetRange(0,($text.Count - 1))
$newText

【讨论】:

  • v5 解决方案很棒,但是 v4- 解决方案不起作用,因为$text 是一个数组([System.Object[]]),而数组没有.GetRange() 方法。
【解决方案2】:

可以使用每个文件的布尔值$IsFirstLine = $True 跟踪第一行,然后在 ForEach-Object 内将其设置为 false。但我认为,用你的管道方法跟踪最后一行是不可能的——在你知道它是最后一行之前,你已经处理了最后一行。

因此,您需要另一个循环来计算行数或缓冲区,以便在您识别出最后一行后撤消更改。

如果文件小到可以读入内存,也许您可​​以使用如下方法:

$Location = "C:\Users\gerhardl\Documents\Tenacity\TEMP\POWERSHELL TESTS"
$Data = "$Location\*.TXT"
$Output = "$Location\Fixed"

Get-Item $Data | ForEach-Object {                   # for each file..

    $Lines = @(Get-Content $_.FullName)             # read all the lines, force array.
    $LinesToProcess = $Lines[1..($Lines.Count - 1)] # get lines except first and last.

    $ProcessedLines = $LinesToProcess | ForEach-Object {    # for each line..

        $Beginning = $_.Substring(0,105)
        $Account = $_.Substring(105,20) -replace "[ABCDEFGHIJKLMNOPQRSTUVWXYZ]", " "
        $End = $_.Substring(125)
        '{0}{1}{2}' -f $Beginning,$Account,$End

    }

    $OutputLines = $Lines[0] + $ProcessedLines + $Lines[-1] # add original first and last

    $OutputLines | Set-Content -Path (Join-Path $Output $_.Name)

}

【讨论】:

  • 我能否像您一样修改答案以包含我的代码?我正在尝试完成这项工作,但无法做到正确 - 我是 PS 的新手,我的技能包括将其他脚本混在一起......请记住,我有单独的文本文件需要写出到相同的单独文件名。
  • 我只是勾勒出来,没试过;但我已将您的代码放入其中并进行了一些调整。
  • 现在正在写出文件,但似乎已删除所有换行符。
  • 哦;将$OutputLines = $Lines[0] + 变成$OutputLines = @($Lines[0]) +
【解决方案3】:

注意:这篇文章回答了如何从处理中排除输入文件/输入集合的第一行和最后一行

Manu's helpful ... | Select-Object -Skip 1 | Select-Object -SkipLast 1 solutionPSv5+ 中效果很好(假设第一行和最后一行应该从输出中删除)。

但是,他们的 PSv4- 解决方案 不起作用(在撰写本文时),因为 Get-Content $file | Select-Object -Skip 1 返回的数组([System.Object[]] 实例)没有 .GetRange() 方法.
这是一个使用 PowerShell 范围运算符 (..) 的有效解决方案:

# Read lines of the input file into an array.
$allLines = Get-Content $file
# Using the range operator (..), get all elements except the first and the last.
$allLines[1..([Math]::Max(1, $allLines.Count-2))]

注意:
* 尝试 [1..-1] 很诱人,但 在 PowerShell 中工作,因为 1..-1 的计算结果为下标 1, 0, -1
* 如果您知道至少有 3 个输入对象,则可以省略 [Math]::Max() 调用。

然而,上述解决方案并不总是一种选择,因为它需要首先收集所有输入对象在内存中,这消除了内存限制,基于管道的解决方案提供的一对一处理
(尽管如果可行的话,内存中的解决方案会更快。)

要在 PSv4- 中解决这个问题,您可以管道友好方式模拟Select-Object -SkipLast 1如下(Select-Object -Skip 1 - 从开始跳过 - PSv4支持-)。

# 'one', 'two', 'three' is a sample array. Output is 'one', 'two'
'one', 'two', 'three' | ForEach-Object { $notFirst = $False } { 
  if ($notFirst) { $prevObj }; $prevObj = $_; $notFirst = $True
}

每个输入对象的输出都会延迟一次迭代,这实际上忽略了最后一次。

这是对-SkipLast <n>概括,实现为高级函数Skip-Last,它使用[System.Collections.Generic.Queue[]] 实例来延迟<n> 对象的输出:

# Works in PSv2+
# In PSv5+, use `Select-Object -SkipLast <int>` instead.
Function Skip-Last {
  <#
  .SYNOPSIS
    Skips the last N input objects provided.
    N defaults to  1.
  #>
  [CmdletBinding()]
  param(
    [ValidateRange(1, 2147483647)] [int] $Count = 1,
    [Parameter(Mandatory = $True, ValueFromPipeline = $True)]$InputObject
  )

  begin { 
    $mustEnumerate = -not $MyInvocation.ExpectingInput # collection supplied via argument
    $qeuedObjs = New-Object System.Collections.Generic.Queue[object] $Count
  }
  process {
    # Note: $InputObject is either a single pipeline input object or, if
    #       the -InputObject *parameter* was used, the entire input collection.
    #       In the pipeline case we treat each object individually; in the
    #       parameter case we must enumerate the collection.
    foreach ($o in ((, $InputObject), $InputObject)[$mustEnumerate]) {
      if ($qeuedObjs.Count -eq $Count) {
        # Queue is full, output its 1st element.
        # The queue in essence delays output by $Count elements, which 
        # means that the *last* $Count elements never get emitted.
        $qeuedObjs.Dequeue()  
      }
      $qeuedObjs.Enqueue($o)
    }
  }
}

注意:在上面的ValidateRange()属性中,使用2147483647而不是[int]::MaxValue,因为PSv2在这种情况下只支持常量

示例调用:

PS> 'one', 'two', 'three', 'four', 'five' | Skip-Last 3
one
two

【讨论】:

    【解决方案4】:

    我设法做到了如下 - 不完全是我发布的内容,但无法做到这一点。第一行和最后一行(标题和结尾记录)的长度要短得多,所以我做了以下操作:

    $Location = "C:\Users\gerhardl\Documents\Tenacity\TEMP\POWERSHELL TESTS"
    $Data = "$Location\*.TXT"
    $Output = "$Location\Fixed"
    
    Get-Item $Data |
        ForEach-Object {
            $file = $_
            $_ | 
                Get-Content | 
                ForEach-Object {
                if ($_.length -gt 30)
                { 
    
                    $Beginning = $_.Substring(0,105)
                    $Account = $_.Substring(105,20) -replace "[ABCDEFGHIJKLMNOPQRSTUVWXYZ]", " "
                    $End = $_.Substring(125)
                    '{0}{1}{2}' -f $Beginning,$Account,$End
                }
                ELSE {
                    $All = $_.Substring(0)
                    '{0}' -f $All
                     }
    
                } |
    
                Set-Content -Path (Join-Path $Output  $file.Name)
    
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-02
      • 1970-01-01
      • 1970-01-01
      • 2014-07-31
      • 2020-12-17
      • 2018-01-03
      • 1970-01-01
      相关资源
      最近更新 更多