PowerShell在清理文件时跟踪更新和拒绝的行



Add-Member,哈希表,数组等让我有点困惑,所以我不确定解决这个问题的最佳方法。我的目标是获取一个input.CSV,执行清理并将清理后的行发送给Fixed.CSV,并将无法处理的"拒绝行"发送给reject. csv,并解释它们被拒绝的原因。

我的原始脚本是根据单个特征(例如缺少帐户ID)从"坏"中分裂"好",但作为我进入清理,还有其他事情会导致一行出错,我不想用。where()将数据读取到内存中并不断"分裂"它-特别是考虑到我想完成只有3个文件(OG-input.CSV, Fixed.CSV, Junk-reject.CSV)。

$data, $rejectData = (Import-CSV $CSV).Where({![string]::IsNullOrEmpty($_."Account ID")}, 'Split')
If($rejectData){
$rejectData | Add-Member -NotePropertyName "Reject Reason" -NotePropertyValue "Account ID missing"
$rejectData | Export-CSV -LiteralPath "$($CSV.DirectoryName)$($CSV.BaseName)_reject.csv" -NoTypeInformation

我的输出文件基本上是在我对上面的$data的每一行执行了一系列步骤之后创建的。

$outputFile = New-Object System.Collections.ArrayList
Foreach($row in $data){
# Do stuff, check using If, make updates, etc.
[void]$outputFile.Add($row)
}
$outputFile | Export-CSV -LiteralPath "$($CSV.DirectoryName)$($CSV.BaseName)Fixed.csv" -NoTypeInformation

我在这一点上想的是,而不是分裂数据最初,我应该只是通过所有行迭代,如果我可以更新它们;我会发送给$outputFixed。如果有无法纠正的错误,我会将它们发送到$outputReject-但这里有一个警告,我想为"拒绝原因"添加一个新列;然后在进行的过程中进行更新。我的意思是,一行被拒绝可能有多种原因,我想跟踪每一个原因。我已经很接近了,但是创建新专栏给我带来了麻烦。我最初打算在第一次添加列时使用Add-Member,然后为每个$row更新该列中的值;像$row."Reject Reason" = "$($row."Reject Reason")|New Reason"一样,因为这让我得到一个管道分隔的原因列表,一行被拒绝。然后我找到了Powershell add-member。添加一个数组列表的成员?这让我想到,也许我可以把"拒绝理由"中的理由本身列成一个列表,而不是简单地划分。然而,我不确定我是否完全理解所提出的答案的细微差别,也不知道什么可能最适合我。

嵌套数组/列表很好,但是您必须考虑如何存储和显示您的数据。

一个CSV文件,像一个表,不能正确处理嵌套的对象,如列表或数组。如果您了解您的数据,并且不介意在读取RejectReason字段时将它从/转换为带分隔符的字符串,那么这可以很好。例如,您可以使用Where-Object的过滤器块查找$outputRejected中具有特定原因的所有条目:

# similar to what you had before
$csv = Import-Csv $path
$report = foreach ($row in $csv) {
$row | Add-Member -NotePropertyName 'RejectCode' -NotePropertyValue ''
if ($row.id -lt 5) { $row.RejectCode = $row.RejectCode+'Too Low|' }
if ($row.id -gt 3) { $row.RejectCode = $row.RejectCode+'Too High|' }
# Output the finalized row
$row
}
# Example: filter by reason code
$OutputRejected | Where-Object {($_.Reason -split '|') -contains 'Too High'}
ID RejectCode       
-- ----------       
4  Too Low|Too High|
5  Too High|  

对于您正在做的事情,这通常工作得很好。您必须小心您的额外分隔字符,但由于您自己定义了RejectCode,因此它不应该是一个问题。


对于更复杂的东西,我倾向于从每个$row创建一个PSCustomObject,并将每个属性设置为我需要的内容。这比使用Add-Member:

更适合我。
$report = foreach ($row in $csv) {

# custom object with manually defined properties
$reportRow = [PSCustomObject][Ordered]@{
ID = $row.ID
Name = $row.Name
Data = # run some commands to fix bad data
Reasons = @() # list object
}
# can edit properties as normal
if ($row.id -lt 5) { $reportRow.Reasons += $row.RejectCode+'Too Low|'  }
if ($row.id -gt 3) { $reportRow.Reasons += $row.RejectCode+'Too High|' }
$reportrow
}

请注意,当您的属性不是简单的值(如字符串或整型)时,powershell的CSV命令倾向于将属性压缩到没有帮助的system.object[]文本中。保存此类嵌套对象的更好选择是使用JSON这样的结构化格式。例:$report | ConvertTo-Json | Out-File $path.

在不看到任何CSV文件的情况下,您可以这样做:

$csvPath  = 'X:Temp'
$original = Import-CSV -Path (Join-Path -Path $csvPath -ChildPath 'OG-input.CSV')
# create a List object to collect the rejected items
$rejects = [System.Collections.Generic.List[object]]::new()
$correct = foreach ($item in $original) {
$reason = $null
if ([string]::IsNullOrWhiteSpace($_.'Account ID'))  { $reason = "Empty 'Account ID' field" }
elseif ($_.'Account ID'.Length -gt 20) { $reason = "'Account ID' field exceeds maximum length" }
# more elseif checks go here
# after all checks are done
if (!$reason) {
# all OK for this row; just output so it gets collected in $correct
$item
}
else {
# it's a rejected item, add an object to the $rejects list
$obj = $item | Select-Object *, @{Name = 'Reason'; Expression = {$reason}}
$rejects.Add($obj)
}
}
# save both files
$correct | Export-Csv -Path (Join-Path -Path $csvPath -ChildPath 'Fixed.CSV') -NoTypeInformation
$rejects | Export-Csv -Path (Join-Path -Path $csvPath -ChildPath 'Junk-reject.CSV') -NoTypeInformation

你需要填写剩下的支票和拒收理由,当然

这是我最后的结果。我认为它可以工作,因为输出看起来像我所期望的。

Foreach($row in $data){
#Process all reject reasons first and reject those rows
If([string]::IsNullOrEmpty($row."Account ID")){
$row | Add-Member -NotePropertyName "Reject Reason" -NotePropertyValue ("$($row."Reject Reason")", "Missing Account ID" -Join "|").TrimStart("|") -Force
}
If([string]::IsNullOrEmpty($row."Service Start Dates") -And ([string]::IsNullOrEmpty($row."Service End Dates"))){
$row | Add-Member -NotePropertyName "Reject Reason" -NotePropertyValue ("$($row."Reject Reason")", "Missing Both Service Dates" -Join "|").TrimStart("|") -Force
}
If(Get-Member -InputObject $row "Reject Reason"){
[void]$outputReject.Add($row)
Continue
}
If([string]::IsNullOrEmpty($row."Birth Date")){
$row."Birth Date" = $dte
}
If([string]::IsNullOrEmpty($row."Gender")){
$row."Gender" = "Female"
}
If( [string]::IsNullOrEmpty($row."Service Start Dates") -And !( [string]::IsNullOrEmpty($row."Service End Dates"))){
$row."Service Start Dates" = $row."Service End Dates"
}
[void]$outputFixed.Add($row)
}
$outputFixed | Export-CSV -LiteralPath "$($inputFile.DirectoryName)$($inputFile.BaseName)Fixed.csv" -NoTypeInformation
If($outputReject){
$outputReject | Export-CSV -LiteralPath "$($inputFile.DirectoryName)$($inputFile.BaseName)RejectedRows.csv" -NoTypeInformation
}

基本上,我仍然收集数组列表中的每一行,一旦整个文件被处理,它将输出。我使用Add-Member与-Force来"覆盖"拒绝原因,并使用.TrimStart("|")来连接文本以摆脱前导管道。这绝对适合我(加上很容易实现我已经写的东西)

相关内容

最新更新