使用 Powershell 从 csv 行中删除非法的 CRLF
Using Powershell to remove illegal CRLF from csv row
温柔Reader,
我在一个目录中存放了一年的供应商 csv 文件。我的任务是将它们作为“历史负载”加载到 SQL 服务器数据库中。这些文件是 mal-formed,当我们与供应商合作 re-send 365 个新的、结构正确的文件时,我的任务是尝试使用我们现有的文件。
我只能使用 C#(作为 SSIS 中的脚本任务)或 Powershell。
每个文件都没有 header 但架构是已知的并内置到 SSIS 包连接中。
每个文件大约有 35k 行,每个文件大约有几十 mal-formed 行。
每个格式正确的行包含 122 列,121 个逗号。
行不是文本限定的。
示例:(清除 PII 的数据)
555222,555222333444,1,HN71232,1/19/2018 8:58:07 AM,3437,27.50,HECTOR EVERYMAN,25-Foot Garden Hose - ,1/03/2018 10:17:24 AM,,1835,,,,,online,,MERCH,1,MERCH,MI,,,,,,,,,,,,,,,,,,,,6611060033556677,2526677,,,,,,,,,,,,,,EVERYMAN,,,,,,,,,,,,,,,,,,,,,,,,,,,,,VWDEB,,,,,,,555666NA118855,2/22/2018 12:00:00 AM,,,,,,,,,,,,,,,,,,,,,2121,,,1/29/2018 9:50:56 AM,0,,,[CRLF]
555222,555222444888,1,CASUAL50,1/09/2018 12:00:00 PM,7000,50.00,JANE SMITH, Casual Gift Card,1/19/2018 8:09:15 AM,1/29/2018 8:19:25 AM,1856,,,,,online,,FREE,1,CERT,GC,,,,,,,6611060033553311[CRLF]
,6611060033553311[CRLF]
,,,,,,,,,25,,,6611060033556677,2556677,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CASUAL25,VWDEB,,,,,,,555222NA118065,1/22/2018 12:00:00 AM,,,,,,,,,,,,,,,,,,,,,,,,1/19/2018 12:00:15 PM,0,,,[CRLF]
555222,555222777666,1,CASHCS,1/12/2018 10:31:43 AM,2500,25.00,BOB SMITH,BIG BANK Rewards Cash Back Credit [...6S66],,,1821,,,,,online,,CHECK,1,CHECK,CK,,,,,,,,,,,,,,,,,,,,555222166446,5556677,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,VWDEB,,,1/23/2018 10:30:21 AM,,,,555666NA118844,1/22/2018 12:00:00 AM,,,,,,,,,,,,,,,,,,,,,,,,1/22/2018 10:31:26 AM,0,,,[CRLF]
Powershell Get-Content(我认为...)将文件读入数组,其中每一行都由 CRLF 标识为终止符。这意味着(我认为)mal-formed 行将被视为数组的一个元素,而不管它包含多少“列”。
C# Streamreader 也使用 CRLF 作为标记,但 streamreader object 也有一些可用的方法,如 Peek 和 Read,可能会有用。
请各位智者指出阻力最小的方向。使用 Powershell,作为处理 mal-formed csv 文件的脚本,以便删除非 EOL 的 CRLF。
谢谢。
老实说,您最好的选择是从供应商那里获得良好的数据。试图解决一团糟只会在以后引起问题。垃圾进垃圾出。既然是你在数据库里写了垃圾数据,那么恭喜你,现在数据库数据质量不好都是你的错。请先与您的经理和利益相关者交谈,以便您在书面协议中说明您没有破坏数据并且数据一开始就被破坏了。我在 ETL 处理上经常看到这样的问题。
一个快速而肮脏的伪代码,没有错误处理、边缘情况处理、子字符串索引假设、性能保证等等,
while(dataInFile)
line = readline()
:parseLine
commasInLine = countCommas(line)
if commasInLine == rightAmount
addLineInOKBuffer(line)
else
commasNeeded = rightAmount - commasInLine
if commasNeeded < 0
# too many commas, two lines are combined
lastCommaLocation = getLastCommaIndex(line, commasNeeded)
addLineInOKBuffer(line.substring(0, lastCommaLocation)
line = line.substring(lastCommaLocation, line.end)
goto :parseline
else
# too few lines, need to read next line too
line = line.removeCrLf() + readline()
goto :parseline
我们的想法是,首先查找一行并计算其中有多少个逗号。如果计数与预期相符,则该行没有中断。将其存储在包含良好数据的缓冲区中。
如果逗号太多,则该行至少包含两个不同的元素。然后找到第一个元素结束位置的索引,将其提取并存储在好数据缓冲区中。然后删除已处理的部分行并重新开始。
如果逗号太少,则该行将被换行符分隔。从文件中读取下一行,将其与当前行合并,然后从计算行数开始再次解析。
基于 @vonPryz 设计,但在 (Native¹) PowerShell 中:
$Delimiters = 121
Get-Content .\OldFile.csv |ForEach-Object { $Line = '' } {
if ($Line) { $Line += ',' + $_ } else { $Line = $_ }
$TotalMatches = ($Line |Select-String ',' -AllMatches).Matches.Count
if ($TotalMatches -ge $Delimiters ) {
$Line
$Line = ''
}
} |Set-Content .\NewFile.Csv
1) 我想通过避免 +=
并使用点 .net 方法和文本流媒体可能会提高性能
温柔Reader,
我在一个目录中存放了一年的供应商 csv 文件。我的任务是将它们作为“历史负载”加载到 SQL 服务器数据库中。这些文件是 mal-formed,当我们与供应商合作 re-send 365 个新的、结构正确的文件时,我的任务是尝试使用我们现有的文件。
我只能使用 C#(作为 SSIS 中的脚本任务)或 Powershell。
每个文件都没有 header 但架构是已知的并内置到 SSIS 包连接中。
每个文件大约有 35k 行,每个文件大约有几十 mal-formed 行。
每个格式正确的行包含 122 列,121 个逗号。
行不是文本限定的。
示例:(清除 PII 的数据)
555222,555222333444,1,HN71232,1/19/2018 8:58:07 AM,3437,27.50,HECTOR EVERYMAN,25-Foot Garden Hose - ,1/03/2018 10:17:24 AM,,1835,,,,,online,,MERCH,1,MERCH,MI,,,,,,,,,,,,,,,,,,,,6611060033556677,2526677,,,,,,,,,,,,,,EVERYMAN,,,,,,,,,,,,,,,,,,,,,,,,,,,,,VWDEB,,,,,,,555666NA118855,2/22/2018 12:00:00 AM,,,,,,,,,,,,,,,,,,,,,2121,,,1/29/2018 9:50:56 AM,0,,,[CRLF]
555222,555222444888,1,CASUAL50,1/09/2018 12:00:00 PM,7000,50.00,JANE SMITH, Casual Gift Card,1/19/2018 8:09:15 AM,1/29/2018 8:19:25 AM,1856,,,,,online,,FREE,1,CERT,GC,,,,,,,6611060033553311[CRLF]
,6611060033553311[CRLF]
,,,,,,,,,25,,,6611060033556677,2556677,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CASUAL25,VWDEB,,,,,,,555222NA118065,1/22/2018 12:00:00 AM,,,,,,,,,,,,,,,,,,,,,,,,1/19/2018 12:00:15 PM,0,,,[CRLF]
555222,555222777666,1,CASHCS,1/12/2018 10:31:43 AM,2500,25.00,BOB SMITH,BIG BANK Rewards Cash Back Credit [...6S66],,,1821,,,,,online,,CHECK,1,CHECK,CK,,,,,,,,,,,,,,,,,,,,555222166446,5556677,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,VWDEB,,,1/23/2018 10:30:21 AM,,,,555666NA118844,1/22/2018 12:00:00 AM,,,,,,,,,,,,,,,,,,,,,,,,1/22/2018 10:31:26 AM,0,,,[CRLF]
Powershell Get-Content(我认为...)将文件读入数组,其中每一行都由 CRLF 标识为终止符。这意味着(我认为)mal-formed 行将被视为数组的一个元素,而不管它包含多少“列”。
C# Streamreader 也使用 CRLF 作为标记,但 streamreader object 也有一些可用的方法,如 Peek 和 Read,可能会有用。
请各位智者指出阻力最小的方向。使用 Powershell,作为处理 mal-formed csv 文件的脚本,以便删除非 EOL 的 CRLF。
谢谢。
老实说,您最好的选择是从供应商那里获得良好的数据。试图解决一团糟只会在以后引起问题。垃圾进垃圾出。既然是你在数据库里写了垃圾数据,那么恭喜你,现在数据库数据质量不好都是你的错。请先与您的经理和利益相关者交谈,以便您在书面协议中说明您没有破坏数据并且数据一开始就被破坏了。我在 ETL 处理上经常看到这样的问题。
一个快速而肮脏的伪代码,没有错误处理、边缘情况处理、子字符串索引假设、性能保证等等,
while(dataInFile)
line = readline()
:parseLine
commasInLine = countCommas(line)
if commasInLine == rightAmount
addLineInOKBuffer(line)
else
commasNeeded = rightAmount - commasInLine
if commasNeeded < 0
# too many commas, two lines are combined
lastCommaLocation = getLastCommaIndex(line, commasNeeded)
addLineInOKBuffer(line.substring(0, lastCommaLocation)
line = line.substring(lastCommaLocation, line.end)
goto :parseline
else
# too few lines, need to read next line too
line = line.removeCrLf() + readline()
goto :parseline
我们的想法是,首先查找一行并计算其中有多少个逗号。如果计数与预期相符,则该行没有中断。将其存储在包含良好数据的缓冲区中。
如果逗号太多,则该行至少包含两个不同的元素。然后找到第一个元素结束位置的索引,将其提取并存储在好数据缓冲区中。然后删除已处理的部分行并重新开始。
如果逗号太少,则该行将被换行符分隔。从文件中读取下一行,将其与当前行合并,然后从计算行数开始再次解析。
基于 @vonPryz 设计,但在 (Native¹) PowerShell 中:
$Delimiters = 121
Get-Content .\OldFile.csv |ForEach-Object { $Line = '' } {
if ($Line) { $Line += ',' + $_ } else { $Line = $_ }
$TotalMatches = ($Line |Select-String ',' -AllMatches).Matches.Count
if ($TotalMatches -ge $Delimiters ) {
$Line
$Line = ''
}
} |Set-Content .\NewFile.Csv
1) 我想通过避免 +=
并使用点 .net 方法和文本流媒体可能会提高性能