【问题标题】:regex to split csv, values surrounded by quotes, values have quotes正则表达式拆分csv,用引号括起来的值,值有引号
【发布时间】:2018-12-16 14:31:41
【问题描述】:

我不太擅长正则表达式,我的任务是使用 powershell 将 csv 加载到数据表中。 csv 的值用引号括起来,用逗号分隔。麻烦的是,有些行由列值组成,值本身包含引号。

$csvSplit = "($csvdelimiter)"
$csvSplit += '(?=(?:[^"]|"[^"]*")*$)'
$regexOptions = [System.Text.RegularExpressions.RegexOptions]::ExplicitCapture

抛出这个问题的行中有值,其中值可能会说像3-1/8" 这样的值作为值的一部分。我也是 powershell 的菜鸟,但我真的不确定如何更改正则表达式以使其省略这些类型的情况。

非常感谢任何解释和帮助!

更新

尝试这些解决方案似乎并没有完全解决问题,只是将问题转移到了不同​​的位置。我被引导相信问题出在 CSV 本身,但我无法找到格式错误的示例。这些答案很棒,我希望将来有人能从阅读这篇文章及其出色的答案中有所收获。谢谢大家。

【问题讨论】:

  • 是否嵌入 " 实例没有转义?你的输入真的有"3-1/8""这样的值吗?
  • 是的,确实如此。我不确定这个 CSV 是如何生成的。
  • 我不太擅长正则表达式,但这似乎可行:"(.*?)"(,|$)。这里的想法是,匹配“双引号后紧跟逗号或行尾”之前的任何内容。 regex101.com/r/4BZkgt/3
  • 看着你发送的链接,我很困惑。它似乎与单元格值匹配。我正在尝试匹配真正的分隔符,以便我可以按它们分割每一行
  • 不要使用正则表达式。使用旨在正确解析 CSV 的东西。

标签: regex powershell csv datatable


【解决方案1】:

假设逗号作为分隔符,这应该可以解决问题:

((Get-Content '.\split.txt' -raw) -split  '"?,"?|^"|"$' -ne '')
  • 我在 , 之前和之后使用可选的 " 拆分
  • 并删除开头和结尾的引号。
  • 为了去除多余的空匹配(参见demo),我使用-ne 运算符。

警告:如果引号不是平衡双引号的一部分,您可能会丢失它。

【讨论】:

  • 干得好;这确实是获取原始字段值数组的最简单方法。
【解决方案2】:

您需要正则表达式吗?内置的 Powershell CSV 转换器是否对您不起作用?

$csv = Get-Content .\split.txt | ConvertFrom-CSV

或类似的东西。我建议您从小于 32Gb 的文件开始测试您的方法。正如其他人提到的,引用数据存在很多陷阱,但只要您的输入格式正确并且您愿意等待 PowerShell 读取 32Gb,这可能对您有用。

【讨论】:

  • 感谢您的建议。我一直在查找它并试图理解它。我现在到底用 $csv 做什么?
  • foreach($row in $csv) { $n = $row.N; $o = $row.O } 这样的东西来循环你的数据?如果您的第一行用作列名,那么在此示例中,第一行将类似于 M,N,O,P 或类似名称。
  • 我明白了。我会玩弄它,看看它是否有效。目前我看到ConvertFrom-CSV 在这个文件上运行需要多长时间。有 617 列(我知道,这很荒谬),我似乎很生气要求它在周日晚上这样做。
  • 使用Get-Content -Head 10000 检查并更改数字。我已经尝试在您提到的格式的数据上使用ConvertFrom-CSV,它不喜欢不匹配的引号。 "3-1/8"","next", 作为一个值读入:3-1/8",this"
  • ConvertFrom-Csv(或Import-Csv,这是Get-Content ... | ConvertFrom-Csv 组合的更好替代品)在这里不起作用的原因是字段内部" 字符。没有转义(它们必须加倍)。
【解决方案3】:

因为" 字符。 嵌入您的字段中没有转义

  • 你不能可靠地使用Import-Csv(或ConvertFrom-Csv)。

    • 用于字段内部" 字符。要正确解析,它们必须表示为 ""(加倍)。
  • 需要手动解析,这只有在您做出假设时才会起作用。

如果可以假设 嵌入 (field-interior) " 从不直接跟随,,您可以尝试以下方法(PSv4+ ):

# Sample array of CSV lines.
# Note that some fields have unescaped internal " chars. 
$csv = @'
"col1","col2"
"one","3-1/0""
"normal","line"
"3-1/1"","two"
"3" of rain","today"
'@ -split '\r?\n'    


$lineNo = 0
# Process the CSV lines one by one.
# Note: Replace `$csv |` with `Get-Content yourFile.csv`
$csv | ForEach-Object {
  # Extract the field values based on the assumption above.
  $fieldValues = ([regex]::Matches($_, '"(.*?)"(?:,|$)')).ForEach({ $_.Groups[1].Value })
  if (++$lineNo -eq 1) { # 1st == header line
    # Create an object *template* with the 1st line's field values as 
    # property names.
    $propNames = $fieldValues
    $ohtAux = [ordered] @{}
    foreach ($propName in $propNames) { $ohtAux[$propName] = $null }
    $objTemplate = [pscustomobject] $ohtAux
  } else { # 2nd and subsequent lines: data lines
    # Clone the template object.
    $obj = $objTemplate.psobject.Copy()
    # Fill the clone's properties with the field values.
    $i = 0
    foreach ($propName in $propNames) { $obj.$propName = $fieldValues[$i++] }
    # Output the clone.
    $obj
  }
}

以上产量:

col1       col2
----       ----
one        3-1/0"
normal     line
3-1/1"     two
3" of rain today

警告:这个解决方案相对,因为必须为每个输入行执行一个脚本块。

注意事项:

  • 正则表达式'"(.*?)"(?:,|$)' 非贪婪地*? 匹配"-封闭值,只要结束" 紧跟, 或(|)行尾( $)。

    • (...) 中包含.*? - 一个捕获组 - 使" 实例之间的字符串(即原始字段值)可用作.Groups 属性的第二个元素(索引1[regex]::Matches() 返回的匹配对象
    • 注意(?:,|$) 中的?: 表示非捕获 组,选择该组是因为以后不需要访问组匹配的内容。除了在以后发出对哪些组感兴趣的信号外,这还使正则表达式更加高效。
    • 注意:wp78de's helpful answer 展示了一种更简单、更快捷的方法,该方法基于使用正则表达式匹配 分隔符 而不是字段值,直接 返回原始字段值。
  • .ForEach({ $_.Groups[1].Value }) 因此输出所有原始字段值并将它们作为数组保存在变量$fieldValues 中。

  • $ohtAux = [ordered] @{}foreach ($propName in $propNames) { $ohtAux[$propName] = $null } 定义了一个带有有序键的辅助哈希表,并为第一输入行的字段值创建(最初为空)条目,假定为列名; [pscustomobject] $ohtAux 然后将哈希表转换为自定义对象,该对象用作要输出的对象的模板,以供后面的数据行使用。

【讨论】:

    猜你喜欢
    • 2015-11-25
    • 2015-03-06
    • 2019-05-11
    • 2014-09-10
    • 2016-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多