【问题标题】:Powershell: Replace headers while using Import-CSVPowershell:使用 Import-CSV 时替换标题
【发布时间】:2021-09-13 17:10:14
【问题描述】:

我找到了一个相关的答案here,这确实很有帮助,但不是我想要的。我还查看了许多其他问题,但不幸的是,我无法弄清楚如何让它工作,而且看起来很简单。

基本上,我正在使用Import-Csv 并处理大量数据;但标题的名称有时会改变。因此,我不想重新编写我的代码,而是将我获得的标头映射到我的代码块中使用的标头。将最终数据输出为 CSV,我可以使用“更新的标题”保留它,或者,如果我能弄清楚如何轻松交换标题,我可以随时将它们交换回原来的样子。

假设我在 Excel 中有一个映射文件。我可以在行或列中进行映射,以更容易为准。对于第一个示例,我有行中的映射。当我使用Import-CSV 时,我想使用第 2 行的标题而不是第 1 行的标题。这是映射文件的内容:

所以基本上,如果我对这一切都进行硬编码,我会得到类似的东西:

$null, $headerRow, $dataRows = (Get-Content -Raw foo.csv) -split '(^.+\r?\n)', 2

ConvertFrom-Csv ($headerRow.Trim() -replace 'Identification', 'ID' -replace 'Revenue Code', 'Revenue_Code' -replace 'Total Amount for Line', 'Amount' -replace 'Total Quantity for Line', 'Qty'), $dataRows

除了我不想硬编码之外,我基本上是在寻找一种方法来使用替换映射文件或哈希表​​,如果我可以创建一个。

#Pseudo code for what I want
$hashtable = Get-Content mapping.xlsx
ConvertFrom-Csv ($headerRow.Trim() -replace $hashtable.Name, $hashtable.Value), $dataRows

我可能会失败并且找不到类似的示例,因为我试图灵活地使用映射文件的格式。我最初的想法是基本上将第一行视为一个字符串,并将整个字符串替换为第二行。但是哈希表的想法来自可能将映射重组为如下所示:

这里我基本上会-replace每个Source值和对应的Target值。

编辑如果您需要转换回来,请试一试 - 但请记住,只有在 Source:Target 值之间存在一对一关系时,它才会起作用。 p>

#Changing BACK to the original Headers...
$Unmap = @{}
(Import-Csv MappingTable.csv).ForEach({$Unmap[$_.Target] = $_.Source})

#Get string data from CSV Objects
$stringdata = $outputFixed | ConvertTo-CSV -NoTypeInformation
$headerRow = $stringdata[0]
$dataRows = $stringdata[1..($stringdata.Count-1)] -join "`r`n"

#Create new header data
$unmappedHeaderRow = ($headerRow -replace '"' -split ',').ForEach({'"' + $Unmap[$_] + '"'}) -join ','

$newdata = ConvertFrom-Csv $unmappedHeaderRow, $dataStrings

【问题讨论】:

  • 如果列标题可以改变,列的顺序可能不会改变?
  • 如果顺序和计数保持不变,我倾向于简单地使用数组索引在所需标题和当前标题之间进行引用。只需从Import-Csv 返回的第一个对象上剥离属性名称,就可以很容易地做到这一点。如果我的场景正确,我可以发布答案。
  • @Steven - 我不认为可以肯定地说顺序和计数将是相同的。一般来说,伯爵应该是一样的。顺序可能不同。

标签: powershell


【解决方案1】:

这是一个基于您最初尝试的完整示例:

  • 它通过(另一个).csv 文件提供列名(标题)映射,列SourceTarget,其中每一行将源名称映射到目标名称,如(也)所示你的问题。

  • 映射 CSV 文件被转换为将源名称映射到目标名称的 hashtable

  • 然后将数据 CSV 文件作为纯文本读取,就像您的问题一样 - 高效但完整 - 拆分为标题行和数据行,并在哈希表的帮助下构建具有映射名称的新标题行.

  • 然后将新的标题行和数据行发送到ConvertFrom-Csv,以根据映射的列(属性)名称进行对象转换。

# Create sample column-name mapping file.
@'
Source,Target
Identification,Id
Revenue Code,Revenue_Code
'@ > mapping.csv

# Create a hashtable from the mapping CSV file
# that maps each Source column value to its Target value.
$map = @{}
(Import-Csv mapping.csv).ForEach({ $map[$_.Source] = $_.Target })

# Create sample input CSV file.
@'
Revenue Code,Identification
r1,i1
r2,i2
'@ > data.csv

# Read the data file as plain text, split into a header line and
# a multi-line string comprising all data lines.
$headerRow, $dataRows = (Get-Content -Raw data.csv) -split '\r?\n', 2

# Create the new header based on the column-name mapping.
$mappedHeaderRow =
  ($headerRow -replace '"' -split ',').ForEach({ $map[$_] }) -join ','

# Parse the data rows with the new header.
$mappedHeaderRow, $dataRows | ConvertFrom-Csv

以上输出如下,表明列已有效映射(重命名):

Revenue_Code Id
------------ --
r1           i1
r2           i2

【讨论】:

  • 我讨厌寻求与速度相关的建议,但您似乎总是对不同的方法有相当的了解。随着我的映射表越来越大,(Import-Csv mapping.csv).ForEach({$map[$_.Source] = $_.Target}) 似乎会更快。您知道使用$map[Source] = Target 或使用$map.Add(Source, Target) 是否有任何性能差异?他们似乎在演示数据和我的时间测量上表现相同,但好奇唯一的区别是使用的语法
  • @immobile2,是的,看起来它们的性能大致相同,但存在行为差异:[$key] = $value 悄悄更新 预先存在的条目,如果存在 @987654336 @,而 .Add($key, $value) 抛出异常(在 PowerShell 中表现为语句终止错误)。但是,出于性能原因要避免的语法是点表示法,PowerShell 也支持哈希表:.$key = $value
  • 顺便说一句:正如我刚刚了解到的那样,降低ForEach-Object 解决方案速度的不是管道本身,而是ForEach-Object 本身的低效实现,以及以下内容解决方法甚至优于.ForEach() 方法:Import-Csv mapping.csv | . { process { $map[$_.Source] = $_.Target } } - 请参阅this blog post 和生成的功能请求GitHub issue #10982
  • 另一边:我看过ConvertFrom-StringData,结合了一个属性样式的输入文件(<key>=<value> 对的行),它直接构造了一个哈希表,但令人惊讶的是,它的性能比基于Import-Csv 的解决方案。
  • 感谢您的反馈!我以前看过博客文章,我想我实际上可能已经将它链接到评论或您在 SO 上回答的其他问题中。我的哈希表不会达到我为管道解决方案牺牲.ForEach() 的可读性的大小。如果我想要 ForEach 时有五六个管道……那可能会改变事情!不过,process 脚本中的时间段在做什么?我认为它会是 Import-Csv mapping.csv | & { process { ... }} 点符号对我来说很陌生,所以我不会使用 .$key 并且你的点不跟踪
【解决方案2】:

这里最简单的做法是处理 CSV,然后将每一行从任何格式转换为新的所需目标格式。

假设我们有这样的输入 CSV。

RowID,MayBeNull,MightHaveAValue
1,,Value1
2,Value2,
3,,Value3

然后我们像这样导入 csv:

#helper function for ugly logic
function HasValue($param){
    return -not [string]::IsNullOrEmpty($param)
}

$csv = import-csv C:\pathTo\this.csv

foreach($row in $csv){
   if (HasValue($row.MayBeNull)){
       $newColumn = $row.MayBeNull
    }
    else{
       $newColumn = $row.MightHaveAValue
    }
    #generate new output
    [psCustomObject]@{
       Id = $row.RowId;
     NewColumn = $newColumn
    }
}

它给出以下输出:

对于数据迁移脚本来说,这是一个易于遵循的模式,然后您只需要扩展它来解决您的问题。

【讨论】:

  • 这似乎可行,但我需要将它放入整个脚本中,看看它让一切慢了多少。我首先使用Import-CSV 和Powershell 的原因是因为我正在清理太大而无法在Excel 中打开/操作的CSV 数据。那么,我最好只需要遍历每一行一次。现在,我正在 Foreach 循环中进行所有清理工作。使用这种方法,我可能需要迭代两次。也许我没有正确表达问题,但我也看不出如何避免在您的示例中对NewColumn 进行硬编码
猜你喜欢
  • 1970-01-01
  • 2011-03-23
  • 1970-01-01
  • 1970-01-01
  • 2016-12-26
  • 2017-10-19
  • 1970-01-01
  • 2014-12-24
  • 1970-01-01
相关资源
最近更新 更多