使用正则表达式分割CSV,值用引号引起来,值带引号

时间:2018-07-08 22:46:30

标签: regex powershell csv datatable

我不太擅长Regex,我的任务是使用Powershell将csv加载到数据表中。 csv的值用引号引起来,用逗号分隔。麻烦的是,某些行由列值组成,这些值本身包含引号。

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

引发这种情况的行中包含值,其中值可能会说诸如3-1/8"之类的值。我也是Powershell的新手,但我真的不确定如何更改正则表达式以使其忽略这些情况。

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

更新

尝试这些解决方案似乎并不能完全解决问题,只是将问题转移到了另一个地方。我被认为是CSV本身的问题,但我还没有找到格式错误的例子。这些答案是很好的答案,我希望将来有人能从阅读本文和它的出色答案中受益。谢谢大家。

3 个答案:

答案 0 :(得分:3)

因为"个字符。您的字段中的嵌入不可逃脱

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

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

如果可以假设嵌入(场内)"永远不会紧跟, ,则可以尝试以下方法(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

注意事项:考虑到必须为每个输入行执行一个脚本块,这种解决方案相对来说 slow 比较慢。

注意:

  • 正则表达式'"(.*?)"(?:,|$)'非贪婪地*?与封闭的"匹配,只要结束"后紧跟着{{1} }或(,)行(|)的结尾。

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

  • .ForEach({ $_.Groups[1].Value })$fieldValues定义带有顺序键的辅助哈希表,并为第一个输入行的字段值创建(最初为空)条目,这些条目假定为列名; $ohtAux = [ordered] @{}然后将哈希表转换为自定义对象,该对象将作为对象的模板输出,以跟随数据行。

答案 1 :(得分:1)

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

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

注意事项:如果报价不是平衡的双引号对的一部分,则可能会丢失报价。

答案 2 :(得分:0)

您需要正则表达式吗?出于某种原因,内置Powershell CSV转换器对您不起作用?

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

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