4

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

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

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

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

更新

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

4

3 回答 3

3

因为"字符。嵌入在您的字段中不会被转义

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

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

如果可以假设embedded (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

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

笔记:

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

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

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

于 2018-07-09T03:52:31.360 回答
1

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

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

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

于 2018-07-09T01:06:14.517 回答
0

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

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

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

于 2018-07-09T01:14:16.417 回答