我有一个以竖线分隔的文本文件。该文件包含各种类型的“记录”。我想为每种记录类型修改某些列。为简单起见,假设有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是否是最佳解决方案。关于如何做到这一点的任何想法?
答案 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 +文件。所有其他实现在大型输入文件上都要慢得多。