如何使用PowerShell修改管道分隔的文本文件的内容

时间:2015-10-12 03:33:18

标签: powershell

我有一个以竖线分隔的文本文件。该文件包含各种类型的“记录”。我想为每种记录类型修改某些列。为简单起见,假设有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的File-class。如果它是一个简单的字符串替换,我会做类似的事情:

$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是否是最佳解决方案。关于如何做到这一点的任何想法?

3 个答案:

答案 0 :(得分: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"||

答案 1 :(得分:1)

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

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值来获得更快的速度,以找到理想的数量。

答案 2 :(得分:1)

鉴于输入文件很大,我不会使用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 +文件。所有其他实现在大型输入文件上都要慢得多。