【问题标题】:Select CSV columns in Powershell where header name contains a specific string在 Powershell 中选择标题名称包含特定字符串的 CSV 列
【发布时间】:2018-01-01 20:58:59
【问题描述】:

我有一个大约 10-15 列的数据文件,我想从中提取特定的列。有些列我知道确切的列标题,而另一些我只知道前两个字母总是“FC”。 如何仅选择我知道列标题的列和以“FC”开头的列? 从“FC”列开始,我尝试过这样:

$myCSV = Import-CSV "mydata.txt" -Delimiter "`t"
$FCcols = $myCSV[0].psobject.Properties | foreach { $_.Name } | Where {$_ -match "FC"}
$myCSV | select $FCcols

但我只是得到一个错误:

Select-Object : Cannot convert System.Management.Automation.PSObject to one of 
the following types {System.String, System.Management.Automation.ScriptBlock}.
At line:3 char:16
+ $myCSV | select <<<<  $FCcols
    + CategoryInfo          : InvalidArgument: (:) [Select-Object], NotSupport 
   edException
    + FullyQualifiedErrorId : DictionaryKeyUnknownType,Microsoft.PowerShell.Co 
   mmands.SelectObjectCommand

那么,如果我尝试:

$myCSV = Import-CSV "mydata.txt" -Delimiter "`t"
$FCcols = [System.Collections.ArrayList]@()
$myCSV[0].psobject.Properties | foreach { $_.Name } | Where {$_ -match "FC"} | %{$FCcols.Add($_)}
$myCSV | select $FCcols

我得到了我想要的输出,除了它是“列标题:值”格式,像这样:

FC1839 : 0
FC1842 : 1
FC1843 : 6
FC1844 : 12
FC1845 : 4

FC1839 : 0
FC1842 : 0
FC1843 : 19
FC1844 : 22
FC1845 : 14

我可能只是遗漏了一些简单的东西,但是我如何才能选择这些匹配的列,然后将它们输出到另一个 .txt 文件(没有标题:值格式)?

【问题讨论】:

  • 为什么不只是$myCSV |Select *FC*
  • 如果你使用 $myCSV | 你所拥有的会起作用选择 $FCcols.Name 但 Mathias 是对的...
  • @bunzab:确实,Mathias 的方法是最好的解决方案。 $FCcols.Name 不起作用,因为 $FCcolsstrings 数组(没有 .Name 属性)。事实上,该代码在 PSv3+ 中按原样运行,并且仅在 PSv2 中失败,因为我认为这是一个错误。

标签: powershell csv


【解决方案1】:

第一件事:Mathias R. Jessen's 有用的提示不仅可以解决您的问题,而且大大简化了方法(并且在 PSv2 中也适用):

$myCSV | Select-Object FC*

(隐含的)-Property 参数支持通配符表达式,因此FC* 匹配所有以FC 开头的属性(列名)。

至于您所看到的输出格式:由于您选择了 5 个属性,PowerShell 默认为隐式 Format-List 格式,每个属性名称-值对位于其自己的行中。

修复此显示问题,请显式传递到Format-Table(如果您选择了 4 个或更少的属性,PowerShell 会隐式执行此操作):

$myCSV | Select-Object FC* | Format-Table

将结果重新导出到 CSV (TSV) 文件

Import-Csv mydata.txt -Delimiter "`t" | Select-Object FC* | 
  Export-Csv myresults.txt -Encoding Utf8 -Delimiter "`t" -NoTypeInformation

这样做没有标题行

Import-Csv mydata.txt -Delimiter "`t" | Select-Object FC* | 
  ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Select-Object -Skip 1 |
    Set-Content myresults.txt -Encoding Utf8

至于您的具体症状

这个问题只出现在 PSv2 中,而且我闻起来像个 bug。

解决方法 是让您的列名数组成为强类型 string 数组 ([string[]]):

[string[]] $FCcols = $myCSV[0].psobject.Properties | % { $_.Name } | ? { $_ -match '^FC' }

请注意,为简洁起见,我使用内置别名 % 代替 ForEach-Object? 代替 Where-Object
另请注意,传递给-match 的正则表达式已更改为^FC,以确保仅匹配FC 开头的列。


您的代码在 PSv3+ 中按原样运行,但可以简化

$FCcols = $myCSV[0].psobject.Properties.Name -match "^FC"

注意.Name 是如何直接应用于.psobject.Properties 的,在v3+ 中会导致.Name 成员在集合的每个项目 上被调用,该功能称为member enumeration

【讨论】:

    【解决方案2】:

    我会使用Get-Member 来获取您的列,如下所示:

    $myCSV = Import-CSV "mydata.txt" -Delimiter "`t"
    $myCSV | select ($myCSV | gm -MemberType NoteProperty | ? {$_.Name -match 'FC'}).Name
    

    【讨论】:

    • 感谢您的回复。虽然它没有完全解决我的问题,但我已经能够在其他地方使用它。一种非常有用的技术。
    【解决方案3】:

    Mathias 的有用评论是选择的最佳方式;简单而优雅 - 不知道这是一种选择。

    $myCSV | Select *FC*,ColumnIKnowTheNameOf
    

    我相信您需要添加Export-Csv 才能回答您的最后一个问题。这是我已经研究过的另一种方法,如果您将来需要查询 csv/类似对象,则使用 Get-MemberNoteProperty

    $myCSV = Import-CSV "mydata.txt" -Delimiter "`t"
    
    # you can get the headings by using Get-Member and Selecting NoteProperty members.
    $FCcols = $myCSV |
                Get-Member |
                Where-Object {$_.MemberType -eq "NoteProperty" -and $_.Name -match "FC"} |
                Select-Object -ExpandProperty Name
    
    # you add names to this array.
    $FCcols += "ColumnIKnowTheNameOf"
    
    $myCSV | Select-Object $FCcols
    
    # to get a tab-delimited file similar to the one you imported, use Export-Csv
    $myCSV | Export-csv "myresults.txt" -Delimiter "`t" -NoTypeInformation
    

    【讨论】:

      【解决方案4】:

      我终于想出了一个“快速而肮脏”的解决方案,我很失望没有早点想出来。

      $myCSV = Import-CSV "mydata.txt" -Delimiter "`t" | select FC*
      for ($i = 0; $i -lt $myCSV.count; $i++){
      $writeline = ($myCSV[$i] | %{$_.PSObject.Properties | %{$_.Value}}) -join "`t"
      ac "myresults.txt" $writeline -Encoding utf8}
      

      第一行给出了我想要的列,然后 for 循环获取每列的值属性并将它们连接为表格行,最后每一行都附加到一个文本文件中。 这可能不是实现结果的教学正确方法,但到目前为止它是有效的。 感谢大家的意见!

      【讨论】:

      • 如何快速、干净且节省内存的解决方案? Import-Csv mydata.txt -Delimiter "`t" | Select-Object FC* | Export-Csv myresults.txt -Encoding Utf8 -Delimiter "`t" -NoTypeInformation(如果您真的不想要标题行,请将ConvertTo-CsvSelect-Object -Skip 1 组合起来并通过管道传递到Set-Content)。此外,未来的读者会感到困惑的是,您的答案在现有答案之上添加的唯一新内容是您在问题中没有要求解决的问题的解决方案(重新导出为 CSV/TSV)。跨度>
      • 对话最终涉及了许多问题,因此可能会造成混乱,但导出到文本文件确实是最初目标的一部分。输出格式使导出变得困难。我选择包含此解决方案,因为它解决了原始任务,包括格式化困难以及能够按以“FC”开头的列名进行选择。
      • 如果选择此作为解决方案似乎否定了其他输入,我深表歉意。那不是本意。 Mathias R. Jessen、mklement0 和 Dave Sexton 的意见有助于我解决这个问题。如果看起来更合适,我可以将 Mathias R Jessen 的答案更改为官方解决方案。
      • SO 受益于重点突出的问题和普遍感兴趣的匹配答案,并展示了最佳实践。您的答案没有解释您的原始症状,仅使用不惯用的解决方案提取列 values,并添加了与您的问题无关的代码,但没有说明。 (虽然可以更改问题,但在给出答案后这样做会使这些问题无效,这就是不鼓励这样做的原因。)我鼓励您接受最符合这些标准的答案(鉴于其中一个答案是我的,我不会推荐)。
      猜你喜欢
      • 1970-01-01
      • 2022-01-17
      • 2017-09-24
      • 2021-01-14
      • 2021-12-01
      • 1970-01-01
      相关资源
      最近更新 更多