【问题标题】:How to modify contents of a pipe-delimited text file with PowerShell如何使用 PowerShell 修改以竖线分隔的文本文件的内容
【发布时间】:2015-10-12 03:33:18
【问题描述】:

我有一个以竖线分隔的文本文件。该文件包含各种类型的“记录”。我想为每种记录类型修改某些列。为简单起见,假设有 3 种记录类型:A、B 和 C。A 有 3 列,B 有 4 列,C 有 5 列。例如,我们有:

A|stuff|more_stuff
B|123|other|x
C|something|456|stuff|more_stuff
B|78903|stuff|x
A|1|more_stuff

我想将前缀“P”附加到所有需要的列。对于 A,所需的列是 2。对于 B,所需的列是 3。对于 C,所需的列是 4。

所以,我希望输出看起来像:

A|Pstuff|more_stuff
B|123|Pother|x
C|something|456|Pstuff|more_stuff
B|78903|Pstuff|x
A|P1|more_stuff

我需要在 PowerShell 中执行此操作。该文件可能非常大。所以,我正在考虑使用.NET 的文件类。如果它是一个简单的字符串替换,我会做这样的事情:

$content = [System.IO.File]::ReadAllText("H:\test_modify_contents.txt").Replace("replace_text","something_else")
[System.IO.File]::WriteAllText("H:\output_file.txt", $content)

但是,在我的特殊情况下并不是那么简单。所以,我什至不确定 ReadAllText 和 WriteAllText 是否是最好的解决方案。关于如何做到这一点的任何想法?

【问题讨论】:

    标签: powershell


    【解决方案1】:

    我会ConvertFrom-Csv 这样您就可以将每一行作为一个对象进行检查。在这段代码中,我确实添加了一个标题,但主要是为了代码的可读性。无论如何,标题已从最后一行的输出中删除:

    $input = "H:\test_modify_contents.txt"
    $output = "H:\output_file.txt"
    $data = Get-Content -Path $input | ConvertFrom-Csv -Delimiter '|' -Header 'Column1','Column2','Column3','Column4','Column5'
    
    $data | % {
       If ($_.Column5) {
          #type C:
          $_.Column4 = "P$($_.Column4)"
       } ElseIf ($_.Column4) {
          #type B:
          $_.Column3 = "P$($_.Column3)"
       } Else {
          #type A:
          $_.Column2 = "P$($_.Column2)"
       }
    }
    
    $data | Select Column1,Column2,Column3,Column4,Column5 | ConvertTo-Csv -Delimiter '|' -NoTypeInformation | Select-Object -Skip 1 | Set-Content -Path $output
    

    它确实为类型 A 和 B 行添加了额外的 |。输出:

    "A"|"Pstuff"|"more_stuff"||
    "B"|"123"|"Pother"|"x"|
    "C"|"something"|"456"|"Pstuff"|"more_stuff"
    "B"|"78903"|"Pstuff"|"x"|
    "A"|"P1"|"more_stuff"||
    

    【讨论】:

    • 条件对我来说没有意义。 ($.Column5) 实现了什么?不应该是 ($.Column1 -eq "C") 吗?
    • if ($_.Column5) 只会测试它是否存在,因为如果存在......它是 C 型。不过,您的条件也可以。
    • 有没有一种干净的方法来删除引号和添加到记录类型 A 和 B 的额外管道字符?
    • 无需更改值以添加 P,而是可以将每行更改为直接输出。例如,输入 A 行:Add-Content -Path $output "$($_.Column1)|P$($_.Column2)|$($_.Column3)n" `。那么根本不需要最后一行。这可能会很慢,我必须生成一个大文件来测试。
    • 我刚刚在一个 1.1 [MB] 文件上测试了您的解决方案,大约需要 4 分钟才能完成。我的实际文件很可能会更大。有没有更好的方法来做到这一点?
    【解决方案2】:

    如果您的文件很大,那么使用 Import-Csv 或 ReadAll 一次读取完整的文件内容可能不是一个好主意。我将使用 Get-Content cmdlet 使用 ReadCount 属性,该属性将一次流式传输文件一行,然后使用正则表达式进行处理。像这样的:

    Get-Content your_in_file.txt -ReadCount 1 | % {
      $_ -replace '^(A\||B\|[^\|]+\||C\|[^\|]+\|[^\|]+\|)(.*)$', '$1P$2'
    } | Set-Content your_out_file.txt
    

    编辑: 这个版本应该输出更快:

    $d = Get-Date
    Get-Content input.txt -ReadCount 1000 | % {
        $_ | % {
            $_ -replace '^(A\||B\|[^\|]+\||C\|[^\|]+\|[^\|]+\|)(.*)$', '$1P$2'
        } | Add-Content output.txt 
    }
    (New-TimeSpan $d (Get-Date)).Milliseconds
    

    对我来说,这在 350 毫秒内处理了 50k 行。通过调整 -ReadCount 值来找到理想的数量,您可能会获得更快的速度。

    【讨论】:

    • 理论上可行,但它的问题在于将内容从 Foreach-Object 管道传输到 Set-Content 的 Join 样式行为。这将导致整个文件在写入 Set-Content 之前保存在内存中。尝试使用 1 gb 文件,powershell 只是挂起并使用我计算机上的所有内存。
    • 看起来不错,但您可能需要确保在开始调用 Add-Content 之前启动/删除文件
    • 在对 1gb 文件进行测试后,结果显示速度并不快。请参阅我的建议,现已更新(借用您的正则表达式)。
    【解决方案3】:

    鉴于输入文件很大,我不会使用 ReadAllText 或 Get-Content。 他们实际上将整个文件读入内存。

    考虑使用类似的东西

    $filename = ".\input2.csv"
    $outfilename = ".\output2.csv"
    
    function ProcessFile($inputfilename, $outputfilename)
    {
        $reader = [System.IO.File]::OpenText($inputfilename)
        $writer = New-Object System.IO.StreamWriter $outputfilename
        $record = $reader.ReadLine()
        while ($record -ne $null)
        {
            $writer.WriteLine(($record -replace '^(A\||B\|[^\|]+\||C\|[^\|]+\|[^\|]+\|)(.*)$', '$1P$2'))
            $record = $reader.ReadLine()
        }
    
        $reader.Close()
        $reader.Dispose()
        $writer.Close()
        $writer.Dispose()
    }    
    
    ProcessFile $filename $outfilename
    

    编辑:在测试了此页面上的所有建议后,我从 Dave Sexton 借用了正则表达式,这是最快的实现。在 175 秒内处理一个 1gb+ 的文件。在大型输入文件上,所有其他实现都明显变慢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-24
      • 1970-01-01
      • 1970-01-01
      • 2012-11-24
      • 1970-01-01
      相关资源
      最近更新 更多