【问题标题】:Parsing a String with quoted Fields like a CSV-line in Powershell解析带引号字段的字符串,如 Powershell 中的 CSV 行
【发布时间】:2021-07-29 01:09:18
【问题描述】:

我必须将变量输入字符串解析为字符串数组。 输入是 CSV 样式的逗号分隔字段列表,其中每个字段都有自己的带引号的字符串。 因为我不想编写自己的成熟 CSV 解析器,所以到目前为止我可以创建的唯一可行的解​​决方案是这个:

$input = '"Miller, Steve", "Zappa, Frank", "Johnson, Earvin ""Magic"""'

Add-Type -AssemblyName Microsoft.VisualBasic
$enc = [System.Text.Encoding]::UTF8
$bytes = $enc.GetBytes($input)
$stream = [System.IO.MemoryStream]::new($bytes)
$parser = [Microsoft.VisualBasic.FileIO.TextFieldParser]::new($stream)
$parser.Delimiters = ','
$parser.HasFieldsEnclosedInQuotes = $true
$list = $parser.ReadFields()

$list

输出如下所示:

Miller, Steve
Zappa, Frank
Johnson, Earvin "Magic"

有没有更好的解决方案可以通过 Powersell 的另一个 .NET 库获得? 在最好的情况下,我可以避免这个额外的字节数组和流。 我也不确定这个 VisualBasic-Assembly 是否可以长期使用。

这里有什么想法吗?

【问题讨论】:

    标签: arrays powershell csv


    【解决方案1】:

    通过一些额外的安全预防措施和防止无意的字符串外推,您可以将Invoke-ExpressionWrite-Output 结合使用,但请注意Invoke-Expression should generally be avoided

    $fieldList = '"Miller, Steve", "Zappa, Frank", "Johnson, Earvin ""Magic""", "Honey, I''m $HOME"'
    
    # Parse into array.
    $fields = (
      Invoke-Expression ("Write-Output -- " + ($fieldList -replace '\$', "`0"))
    ) -replace "`0", '$$'
    

    注意:

    • -replace '\$', "`0" 临时替换文字 $ 字符。在带有 NUL 字符的输入中。防止意外(或恶意)string expansion (interpolation);第二个-replace 操作恢复原来的$ 字符。
      有关基于正则表达式的-replace 运算符的更多信息,请参阅this answer

    • Write-Output -- 添加到结果字符串并通过Invoke-Expression 将结果解释为PowerShell 命令 导致Write-Output 将字符串的其余部分解析为单独的参数并按原样输出。 -- 确保任何碰巧看起来像 Write-Output自己的 参数的参数都被这样解释。

    • 当且仅当保证输入字符串永远不会包含嵌入的$ 字符,解决方案可以简化为:

      $fields = Invoke-Expression "Write-Output -- $fieldList" 
      

    输出$fields 会产生以下结果:

    Miller, Steve
    Zappa, Frank
    Johnson, Earvin "Magic"
    Honey, I'm $HOME
    

    约束说明和列表

    解决方案依赖于将输入字符串作为 string 的一部分,其 content 是语法上有效的 Write-Output 调用,输入字符串用作后者的 参数Invoke-Expression 然后评估此字符串,就好像它的内容已直接作为命令提交,因此执行Write-Output 命令。根据 PowerShell 解析命令参数的方式,这意味着以下约束:

    • 支持的字段分隔符:

      • 要么:,-separated(每个字段(未加引号)的前导和/或尾随空格被删除,如上所示)。

      • 或者:空格分隔,在字段之间使用一个或多个空格字符。

    • 嵌入字段的非/引用

      • 字段可以被引用

        • 如果单引号 ('...'),字段-内部 ' 字符必须转义为''

        • 如果双引号,字段-internal " 字符必须转义为""`"

      • 字段也可以不加引号

        • 但是,此类字段不得包含任何 PowerShell 参数模式元字符(其中,< > @ # 只是令牌开始处的元字符):

           <space> ' " ` , ; ( ) { } | & < > @ #        
          

    替代方案,通过ConvertFrom-Csv

    iRon's helpful answer 显示基于ConvertFrom-Csv 的解决方案,假设输入字符串中嵌入的字段列表是逗号-分隔的(,):

    • 一方面,它的局限性在于它只支持"..."-quoting of fields和""-escaping of field-internal ",并且不支持由不同数量的空格分隔的字段(仅)。

    • 另一方面,它更灵活,因为它支持字段之间的任何单字符分隔符(不考虑每个字段附带的前导/尾随空格),可以通过以下方式指定-Delimiter 参数。

    使解决方案尴尬的是需要预测最大值。嵌入字段的数量并为它们提供虚拟标题(列名)(-Header (0..99))以使ConvertFrom-Csv工作,这既脆弱又可能造成浪费。 p>

    但是,一个简单的技巧可以绕过这个问题:提交输入字符串两次,在这种情况下ConvertFrom-Csv 将输入字符串中的字段同时视为列名 em> 作为唯一输出行(对象)的列值,然后可以查询其值:

    $fieldList = '"Miller, Steve", "Zappa, Frank", "Johnson, Earvin ""Magic""", "Honey, I''m $HOME"'
    
    # Creates the same array as the solution at the top.
    $fields = ($fieldList, $fieldList | ConvertFrom-Csv).psobject.Properties.Value
    

    【讨论】:

    • 两次提交输入字符串确实是一个聪明的解决未知数量的列名?
    【解决方案2】:

    如果列表有限,您可以使用ConvertFrom-Csv cmdlet 的解析器,例如:

    $List = '"Miller, Steve", "Zappa, Frank", "Johnson, Earvin ""Magic""", "Honey, I''m $HOME"'
    ($List | ConvertFrom-Csv -Header (0..99)).PSObject.Properties.Value.Where{ $Null -ne $_ }
    Miller, Steve
    Zappa, Frank
    Johnson, Earvin "Magic"
    Honey, I'm $HOME
    

    【讨论】:

    • +1 表示这个想法,尽管 -Header (0..99) 部分有点尴尬。一个简单的解决方案是提供输入字符串两次($List, $List | ConvertFrom-Csv).psobject.Properties.Value
    猜你喜欢
    • 1970-01-01
    • 2017-07-10
    • 1970-01-01
    • 1970-01-01
    • 2013-01-10
    • 1970-01-01
    • 2013-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多