【问题标题】:Formatting large text file in Windows Powershell在 Windows Powershell 中格式化大文本文件
【发布时间】:2015-05-17 18:59:36
【问题描述】:

我正在尝试格式化 0 到 3 列之间的大型文本文件(~300MB):

12345|123 Main St, New York|91110
23456|234 Main St, New York
34567|345 Main St, New York|91110

输出应该是:

000000000012345,"123 Main St, New York",91110,,,,,,,,,,,,
000000000023456,"234 Main St, New York",,,,,,,,,,,,,
000000000034567,"345 Main St, New York",91110,,,,,,,,,,,,

我是 powershell 的新手,但我读过我应该避免使用 Get-Content,所以我使用的是 StreamReader。还是太慢了:

function append-comma{} #helper function to append the correct amount of commas to each line


$separator = '|'
$infile = "\large_data.csv"
$outfile = "new_file.csv"

$target_file_in = New-Object System.IO.StreamReader -Arg $infile

If ($header -eq 'TRUE') {
    $firstline = $target_file_in.ReadLine() #skip header if exists
}

while (!$target_file_in.EndOfStream ) {

    $line = $target_file_in.ReadLine() 
    $a = $line.split($separator)[0].trim()
    $b = ""
    $c = ""
    if ($dataType -eq 'ECN'){$a = $a.padleft(15,'0')}
    if ($line.split($separator)[1].length -gt 0){$b = $line.split($separator)[1].trim()}
    if ($line.split($separator)[2].length -gt 0){$c = $line.split($separator)[2].trim()}
    $line = $a +',"'+$b+'","'+$c +'"'
    $line -replace '(?m)"([^,]*?)"(?=,|$)', '$1' |append-comma >> $outfile
}

$target_file_in.close()

我正在为团队中的其他人构建它,并希望使用本指南添加一个 gui: http://blogs.technet.com/b/heyscriptingguy/archive/2014/08/01/i-39-ve-got-a-powershell-secret-adding-a-gui-to-scripts.aspx

在 Powershell 中是否有更快的方法来执行此操作? 我使用 Linux bash(Windows 上的 Cygwin64)编写了一个脚本,并在 Python 中编写了一个单独的脚本。两者都运行得更快,但我正在尝试编写一些可以在 Windows 平台上“批准”的脚本。

【问题讨论】:

  • 第一个字段中的数字总是有 5 位数字吗?此外,是否需要修剪(即是否存在具有前导/尾随空格的字段的可能性)?
  • 第一个字段可以是 1-15 个字符,但在 leftpad 之后总共应该是 15 个字符。修剪是必要的。

标签: shell powershell


【解决方案1】:

所有这些拆分和替换所花费的时间方式比您从 StreamReader 获得的时间要多。下面的代码对我来说将执行时间缩短到了 ~20%:

$separator = '|'
$infile    = "\large_data.csv"
$outfile   = "new_file.csv"

if ($header -eq 'TRUE') {
  $linesToSkip = 1
} else {
  $linesToSkip = 0
}

Get-Content $infile | select -Skip $linesToSkip | % {
  [int]$a, [string]$b, [string]$c = $_.split($separator)
  '{0:d15},"{1}",{2},,,,,,,,,,,,,' -f $a, $b.Trim(), $c.Trim()
} | Set-Content $outfile

【讨论】:

  • 我想使用它,但 Get-Content 会将整个文件读入内存。在大文件上使用它时出现内存不足错误。
  • @Liturgist 使用这样的管道的全部目的是避免将整个文件读入内存。请使用您的实际代码和证据发布一个新问题。
【解决方案2】:

这对您有何帮助?我能够在一个便宜的 ole 工作站上在大约 40 秒内读取和处理一个 35MB 的文件。

文件大小:36,548,820 字节

处理时间:39.7259722 秒

Function CheckPath {
[CmdletBinding()]
    param(
        [Parameter(Mandatory=$True,
        ValueFromPipeline=$True)]
        [string[]]$Path
    )
    BEGIN {}
    PROCESS {
        IF ((Test-Path -LiteralPath $Path) -EQ $False) {Write-host "Invalid File Path $Path"}
    }
    END {}
}

$infile = "infile.txt"
$outfile = "restult5.txt"

#Check File Path
CheckPath $InFile

#Initiate StreamReader
$Reader = New-Object -TypeName System.IO.StreamReader($InFile);

#Create New File Stream Object For StreamWriter
$WriterStream = New-Object -TypeName System.IO.FileStream(
 $outfile,
 [System.IO.FileMode]::Create,
 [System.IO.FileAccess]::Write);

#Initiate StreamWriter
$Writer = New-Object -TypeName System.IO.StreamWriter(
 $WriterStream,
 [System.Text.Encoding]::ASCII);

If ($header -eq $True) {
    $Reader.ReadLine() |Out-Null #Skip First Line In File
}

while ($Reader.Peek() -ge 0) {
    $line = $Reader.ReadLine() #Read Line
    $Line = $Line.split('|') #Split Line
    $OutPut = "$($($line[0]).PadLeft(15,'0')),`"$($Line[1])`",$($Line[2]),,,,,,,,,,,,"
    $Writer.WriteLine($OutPut)
}

$Reader.Close();
$Reader.Dispose();
$Writer.Flush();

$Writer.Close();
$Writer.Dispose();

$endDTM = (Get-Date) #Get Script End Time For Measurement

Write-Host "Elapsed Time: $(($endDTM-$startDTM).totalseconds) seconds" #Echo Time elapsed

【讨论】:

  • 您的代码正是我所需要的。流读/写规避了内存过载。 275MB 文件大约需要 910 秒。这应该添加到代码的顶部以检索最终“经过时间”计算的开始时间:$startDTM = (Get-Date) #Get Script Start Time For Measurment
【解决方案3】:

正则表达式很快:

$infile = ".\large_data.csv"
gc $infile|%{ 
    $x=if($_.indexof('|')-ne$_.lastindexof('|')){
        $_-replace'(.+)\|(.+)\|(.+)',('$1,"$2",$3'+','*12)
    }else{
        $_-replace'(.+)\|(.+)',('$1,"$2"'+','*14)
    }
    ('0'*(15-($x-replace'([^,]),.+','$1').length))+$x
}

【讨论】:

    【解决方案4】:

    我有另一种方法。让 powershell 将输入文件读取为 csv 文件,并使用管道字符作为分隔符。然后按照您想要的方式格式化输出。我没有测试过大文件的速度。

    $infile = "\large-data.csv"
    $outfile = "new-file.csv"
    
    import-csv $infile -header id,addr,zip -delimiter "|" |
    % {'{0},"{1}",{2},,,,,,,,,,,,,' -f $_.id.padleft(15,'0'), $_.addr.trim(), $_.zip} |
    set-content $outfile
    

    【讨论】:

    • 这并没有涵盖 OP 真正寻找的数据操作。他还对使用大文件进行了很好的研究。如果您想让它工作,您需要生成 OP 所需的输出,并且查看Measure-Command 以比较结果以查看它是否更快或至少具有可比性并不是一个坏主意。如果您愿意,可以从 mockaroo.com 开始获取源数据。
    • 好的,我已经根据你的评论重写了这个。我不确定在 Windows 平台中哪个目标最重要,速度还是接受度。
    • 您是对的,OP 确实在问 2 个问题,但现在您至少涵盖了这两个问题。这样更好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-05
    • 2010-10-02
    • 2021-06-23
    • 2018-06-15
    • 1970-01-01
    相关资源
    最近更新 更多