【问题标题】:Powershell: Import-csv, rename all headersPowershell:导入-csv,重命名所有标题
【发布时间】:2021-09-27 04:09:42
【问题描述】:

在我们公司,有许多用户和许多应用程序访问受限,并且数据库具有这些访问权限的证据。我无权访问该数据库,但我所拥有的是自动生成(每天一次)的 csv 文件,其中包含我所有用户的所有访问权限。我希望他们有机会检查他们的访问情况,所以我为此编写了一个简单的 powershell 脚本。

CSV:

user;database1_dat;database2_dat;database3_dat
john;0;0;1
peter;1;0;1

我能做到:

import-csv foo.csv | where {$_.user -eq $user}

但这会向我展示原始的丑陋头饰(带有“_dat”后缀)。当我无法预测明天会有多少标题时,我可以从每个以“_dat”结尾的标题中删除最后四个字符吗?

我知道计算属性如下:

Select-Object @{ expression={$_.database1_dat}; label='database1' }

但据我所知,我必须知道所有列名。

我是否被定罪为通过单独的函数“过度设计”它并从头开始动态构建整个“计算的属性表达式”,或者我是否缺少一种简单的方法?

谢谢:-)

【问题讨论】:

    标签: powershell header rename import-csv


    【解决方案1】:

    假设文件foo.csv 适合整个内存,以下解决方案表现良好:

    $headerRow, $dataRows = (Get-Content -Raw foo.csv) -split '\r?\n', 2
    
    # You can pipe the result to `where {$_.user -eq $user}`
    ConvertFrom-Csv ($headerRow -replace '_dat(?=;|$)'), $dataRows -Delimiter ';' 
    
    • Get-Content -Raw 将整个文件读入内存,比逐行读取(默认)快得多。

    • -split '\r?\n', 2 将生成的多行字符串分成两部分:标题行和所有剩余行。

      • 正则表达式 \r?\n 匹配换行符(CRLF (\r\n) 和仅 LF 换行符 (\n))
      • , 2 将要返回的标记数量限制为2,这意味着一旦找到第一个标记(标题行),拆分就会停止,输入字符串的其余部分(包括所有数据行)按原样返回作为最后一个令牌。
      • 注意$null作为多重赋值中的第一个目标变量,它用于丢弃由字符串开头的分隔符正则表达式匹配产生的标记。
    • $headerRow -replace '_dat(?=;|$)'

      • -replace '_dat(?=;|$)' 使用正则表达式删除任何 _dat 列名后缀(后跟 ; 或字符串的结尾);如果子字符串 _dat 仅作为名称出现 suffix (而不是 inside 名称),您可以简化为 -replace '_dat'
    • ConvertFrom-Csv直接接受字符串的数组,所以清理后的标题行和包含所有数据行的字符串可以按原样传递。


    替代解决方案:对象属性的算法重命名

    注意:此解决方案缓慢,但如果您仅从 CSV 文件中提取 少数 个对象,则可能是一种选择。

    正如您在问题中所指出的,在您的情况下不能选择使用 Select-Objectcalculated properties,因为您事先既不知道列名也不知道它们的编号。

    但是,您可以使用ForEach-Object 命令,其中使用.psobject.Propertiesintrinsic member,用于反射输入对象:

    Import-Csv -Delimiter ';' foo.csv | where { $_.user -eq $user } | ForEach-Object {
      # Initialize an aux. ordered hashtable to store the renamed
      # property name-value pairs.
      $renamedProperties = [ordered] @{}
      # Process all properties of the input object and
      # add them with cleaned-up names to the hashtable.
      foreach ($prop in $_.psobject.Properties) {
        $renamedProperties[($prop.Name -replace '_dat(?=.|$)')] = $prop.Value
      }
      # Convert the aux. hashtable to a custom object and output it.
      [pscustomobject] $renamedProperties
    }
    

    【讨论】:

    • 您认为这将是性能最佳的解决方案吗?我正在寻求实现类似的东西,MSDN 上的解决方案可以说效率要低得多;所以看看这个解决方案是否是“最好的”,或者使用 .Where() 和其他一些调整是否会更快
    • 我在想$headerRow, $dataRows = (Get-Content -ReadCount 0 foo.csv).Where({$_}, 'Split', 1),但它似乎并没有更快,但它的输出似乎与-split 不同,或者至少与使用 -Raw 不同。
    • @immobile2,如果将文件的其余部分作为单个多行字符串获取是可以接受的,那么第一个解决方案是我所知道的最快的解决方案。虽然-ReadCount 0 如果您可以作为一个整体 处理结果数组,则可以加快速度,.Where() 必须再次枚举 数组,并执行脚本块 (@987654359 @) 对于每个枚举元素都很慢 - 您也可以使用 $headerRow, $dataRows = Get-content foo.csv,尽管您可以使用 $headerRow, $dataRows = [IO.File]::ReadAllLines((Convert-Path foo.csv)) 显着加快速度
    • 评论可能太长了,如果您认为单独的答案对未来的搜索者有帮助,请告诉我。我终于了解了您的-split 真正在做什么,事实上您将第一个真正的行视为分隔符本身,然后将其返回很巧妙;但可能比必要的复杂,或者至少我需要更长的时间才能掌握实际发生的事情。考虑这个替代方案:$null, $headerRow, $dataRows = (gc -Raw foo.csv) -split '^', 3, 'multiline' 你甚至可以使用((gc -Raw foo.csv) -split '^', 3, 'multiline').Trim() 简化一些事情
    • @immobile2,是的,太复杂了;最简单的解决方案是-split '\r?\n', 2,这也消除了将$null 作为第一个接收变量的需要——这种简化已经是(最近更新的)答案的一部分。
    【解决方案2】:

    你可以这样做:

    $textInfo = (Get-Culture).TextInfo
    $headers = (Get-Content .\test.csv | Select-Object -First 1).Split(';') |
    ForEach-Object {
        $textInfo.ToTitleCase($_) -replace '_dat'
    }
    
    $user = 'peter'
    
    Get-Content .\test.csv | Select-Object -Skip 1 |
    ConvertFrom-Csv -Delimiter ';' -Header $headers |
    Where-Object User -EQ $user
    
    User  Database1 Database2 Database3
    ----  --------- --------- ---------
    peter 1         0         1        
    

    效率不是很高,但可以解决问题。

    【讨论】:

    • 我最后使用了 mklement0 的解决方案,但这对于我的问题也是完全有效的解决方案。我可以在未来的一些项目中使用这种方法。谢谢你,真的。
    猜你喜欢
    • 2014-03-22
    • 2016-07-28
    • 1970-01-01
    • 2019-03-30
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多