【问题标题】:Multiple global find-replace in CSV filesCSV 文件中的多个全局查找替换
【发布时间】:2015-11-19 03:28:01
【问题描述】:

我一直在努力解决我认为非常简单的问题,但我看不到它。我有一堆 30 多个不同内容的 csv 文件,这些文件每天由不同的应用程序生成,我需要在导入单个报告数据库之前对其进行规范化。一种提取、转换和加载 (ETL) 类型的事物 - 全局查找和替换。

遍历文件没有问题 - 不确定使用 ForEach-Object Fullname 是否是最好的方法,因为输出到“OUT”文件夹会搞砸,但使用 -Name 意味着我必须包含路径。

基本上,所有“真”/“假”文本都将替换为 1/0,与“是”/“否”、开机/关机等相同。此外,我们有 4 个站点 - 每个站点都需要替换为参考。 id,很多类似的东西。我试过修改我在网上找到的大量脚本——很多都在这里。尝试使用数组中的替换文本,将 CSV 拉入字符串,只是看不到它。多年来,我一直在用 VBScript 做同样的事情,这很容易。但我需要学习 PowerShell,所以我会坚持下去。

【问题讨论】:

  • 您能否给我们展示至少一个示例,其中包含一些用于测试的示例文本。只是听起来就像使用Get-Content.Replace() 的例子,正如你所看到的,它被很好地覆盖了。即使在这里:stackoverflow.com/questions/17144355/…
  • 您知道哪些列名包含您要查找的数据吗?如果您这样做/您想定位特定列,只需使用import-csv 获取数据,循环遍历目标列中的替换值,然后使用export-csv 保存回文件。如果您想替换出现在任何地方的值,请使用get-contentset content,使用正则表达式进行替换;只要您的分隔符不包含在文本中(即true/false 和逗号分隔文件的情况),如果您要针对这些的任何外观,这可能是最简单的选择。跨度>
  • 大家好,感谢您的快速回复。这些是简单的 csv 文件,列数从 5 列到 45 列不等。它们主要包含公司机密数据,因此尽管我了解您想要什么,但无法将它们放到网上。我确实看过并且可以对一个值进行简单的查找/替换-例如更改true => 1,但是我是否要重新解析文件以获取false => 0?我在每个文件中有很多数据,每个文件中可能有 7 或 8 个值要替换,主要是 1/0 - db 字段是布尔值。有些用缩写代替。看看我能做些什么来获得一个例子。
  • 我的脚本 - 整个上午都被黑了,所以可能很坏 - 页面不允许我添加它 - 太长了。

标签: powershell csv replace


【解决方案1】:

好的,这里有一个快速搜索和替换功能。它可以读取多个 CSV 文件并匹配\替换多个值。

function Replace-CsvValue
{
    [CmdletBinding()] # Enable pipeline support
    Param
    (
        # Filename, mandatory, takes pipeline input
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        # Alias, allows to directly pipe Get-ChildItem output to this function
        [Alias('FullName')]
        [string]$File,

        # Scriptblock, mandatory, does actual search and replace
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [scriptblock]$ScriptBlock
    )

    Process
    {
        # Import CSV
        $CsvFile =  $File | Import-Csv

        # Generate new filename
        $NewFileName = Join-Path -Path (Split-Path -Path $File -Parent) -ChildPath ('Processed_' + (Split-Path -Path $File -Leaf))

        # Iterate over each line in CSV
        $CsvFile | ForEach-Object {
            # Execute scritblock against record
            & $ScriptBlock
        }

        # Export CSV
        $CsvFile | Export-Csv -Path $NewFileName -NoTypeInformation
    }
}

用法:

  • 使用所需的替换逻辑编写脚本块
  • 管道文件名或Get-ChildItem 输出到函数并传递脚本块

示例:

原始 CSV 文件:

State, Active, Available
PoweredOn, True, Yes

函数调用:

# Scriptblock with replace logic
$ReplaceRule = {

    # Iterate over each item in CSV line
    $Item = $_
    $_.PSObject.Properties.Name | ForEach-Object {

        # If item name matches...
        switch ($_)
        {
            'State' {
                # If item value matches...
                if($Item.$_ -eq 'PoweredOn')
                {
                    $Item.$_ = 'Online'
                }
                # Or if item value matches...
                elseif($Item.$_ -eq 'PoweredOff')
                {
                    $Item.$_ = 'Offline'
                }
                break
            }

            # More replace rules, you can add your own here...

            'Active' {
                if($Item.$_ -eq 'True')
                {
                    $Item.$_ = '1'
                }
                elseif($Item.$_ -eq 'False')
                {
                    $Item.$_ = '0'
                }
                break
            }

            'Available' {
                if($Item.$_ -eq 'Yes')
                {
                    $Item.$_ = '1'
                }
                elseif($Item.$_ -eq 'No')
                {
                    $Item.$_ = '0'
                }
                break
            }
        }
    }
}

# Get all CSV files that match wildcard and
# feed them to the Replace-CsvValue function

Get-ChildItem -Path '.\' -Filter '*Report*.csv' | Replace-CsvValue -ScriptBlock $ReplaceRule

已处理的 CSV 文件:

"State","Active","Available"
"Online","1","1"

【讨论】:

  • 好的,非常感谢。我一直在看:$FileContent -replace 'True', '1' -replace 'False', '0' -replace 'Yes', '1' -replace 'No', '0' -replace 'PoweredOn', '1' -replace 'PoweredOff', '0' -replace 'Running', '1' -replace 'NotRunning', '0' -replace 'anyoldrubbish', '1' $FileContent | Set-Content $Path\OUT\$file 使用的反引号 - 不显示为代码 - 我做错了什么?
  • @user3681382 cmets 中的代码按预期工作,没有比这更漂亮的了。而且我个人反对使用 $FileContent -replace 进行愚蠢的搜索和替换 - 它不了解 CSV 文件结构并且很容易破坏它。
  • 好的,谢谢。在谚语中发现 PShell 有点痛苦,tbh。不过,要坚持下去。
【解决方案2】:

我在Mockaroo 的帮助下制作了这个 csv 用于测试。注意某人的名字是 True。我在那里检查以确保我的逻辑正常工作。

Present Name             Lunch State      
------- ----             ----- -----      
TRUE    Jesse Daniels    No    Powered Off
FALSE   Debra Cunningham Yes   Powered Off
TRUE    True Jones       Yes   Powered Off
TRUE    George Fernandez Yes   Powered Off
FALSE   Lisa Cox         No    Powered On 

为此,我认为忽略它是 CSV 的事实并直接替换文本会很简单。我们必须注意的是部分匹配。使用正则表达式,我们应该能够解释这种可能性。

从 cmets 你已经知道你可以链接-replace。让我们在其中添加一些正则表达式的魔力,以使过程更容易。

$filename = "C:\temp\MOCK_DATA.csv"
$oneKeywordPattern = "Powered On","Yes","True" -join "|"
$zeroKeywordPattern = "Powered Off","No","False" -join "|"
(Get-Content $filename) -replace "(?<=^|,)$oneKeywordPattern(?=$|,)","1" -replace "(?<=^|,)$zeroKeywordPattern(?=$|,)","0" | Set-Content $filename

为了确保考虑到 csv 结构,我们仅在元素位于行首或行尾或逗号后面的逗号时替换(这是使用 lookahead and lookbehind.)。这也确保了我们只更改完整元素并且 True J​​ones 不受影响。

我们使用了$oneKeywordPattern,以便您可以将需要更改为 1 的元素添加到数组中。我们用管道将它们连接起来,以便将其视为替代正则表达式模式。其对应的$zeroKeywordPattern 功能相同。

输出

Present Name             Lunch State
------- ----             ----- -----
1       Jesse Daniels    0     0    
0       Debra Cunningham 1     0    
1       True Jones       1     0    
1       George Fernandez 1     0    
0       Lisa Cox         0     1    

您可能有其他模式不需要使用此逻辑进行更改。只需链接另一个 -replace 并记住它支持正则表达式,因此请注意特殊字符。

这里的两个警告是,如果文件很大,加载文件和处理正则表达式可能需要一段时间(特别是如果您添加更多。)此外,如果您的文本用引号引起来,我们目前不考虑但这很容易。

【讨论】:

  • 您的代码仅替换此 CSV 中的 Lunch"Present","Name","Lunch","State" "TRUE","Jesse Daniels","No","Powered Off"
  • @beatcracker 我会再次测试,但这不是我的发现......我刚刚再次测试,它对我来说工作正常。
  • 我确定你已经测试过了,但我无法让它工作,抱歉。顺便说一句,您的正则表达式是否知道引号内的逗号值,如下所示:"My, oh my"?
  • @beatcracker 对于 OP 正在寻找的值,这不是问题,我会在风险图表上说低。目前它不会破坏任何东西。
  • 如果有一个像这样的单元格“我的,哦,我的,真的,我会说”。那会是个问题
【解决方案3】:

基本上,所有“真”/“假”文本都将替换为 1/0,与“是”/“否”、开机/关机等相同。此外,我们有 4 个站点 - 每个站点都需要替换为参考。 id,很多类似的东西。我试过修改我在网上找到的大量脚本——很多都在这里。尝试使用数组中的替换文本,将 csv 拉成字符串,只是看不到它。多年来我一直在用 vbscript 做同样的事情,这很容易。但我需要学习 PShell,所以我会坚持下去。我真的很感谢这里的一些帮助。

如果它是静态的,你可能会逃脱:

$changes = @{
  'true' = '1';
  'false' = '0';
  'poweredon' = '1';
  'poweredoff' = '0'
}
$folder = "" # your folder here
$csvFiles = ls $folder *.csv
foreach ($file in $csvFiles) {
  $csvData = import-csv $file
  foreach ($row in $csvData) {
    $cells = $row | `
       gm | `
       ?{$_.MemberType -eq 'NoteProperty'} | `
       select -exp Name
    foreach ( $cell in $cells ) {
       $val = $row."$cell"
       $valueNeedsChanging = $changes.ContainsKey($val)
       if ( $valueNeedsChanging ) {
          $newValue = $changes[$val]
          $row."$cell" = $newValue
       }
    }
  }
  cp $file.FullName "$($file.FullName).bak" # back it up before saving
  $csvData | export-csv -Path $file.FullName -NoTypeInformation
}

我选择使用 Import-Export-CSV 来为具有大量高级格式的文件保留 CSV 文件的结构。

【讨论】:

  • 感谢 Carlos - 这很奏效。我需要将$csvData = import-csv $file 更改为$file.FullName,但效果很好。非常感谢。
  • 没问题!乐意效劳。有机会的话,你能把它标记为答案吗?
猜你喜欢
  • 1970-01-01
  • 2014-10-29
  • 1970-01-01
  • 2011-07-29
  • 2014-04-08
  • 2017-07-19
  • 2013-01-29
  • 1970-01-01
  • 2014-08-22
相关资源
最近更新 更多