【问题标题】:Powershell build an array of all possible combinations from all columns in CSV filePowershell 从 CSV 文件中的所有列构建所有可能组合的数组
【发布时间】:2021-03-19 12:07:19
【问题描述】:

TL;DR:如何从“CSV 示例”创建一个类似“最终结果”的数组?

背景
我正在构建一个用于测试目的的实验室文件系统,并且我想创建一个看起来有点像真实文件系统的文件夹结构。我有几个包含文件夹信息的 CSV 文件。

CSV Example:

Department       Level1                  Level2                Level3
Human Resources  Personnel               Templates             APAC
                 Job Applications        Customer Relations    EMEA
                 Salaries and Expenses   Directors             NA
                 Vacation Tracking       Human Resources       SA
                 Disputes                Legal Services
                                         Marketing
                                         Production
                                         Finance
                                         IT Services

我想采用上面的每个组合并创建一个包含以下所有文件夹的文件系统。

End Result:

Human Resources\Personnel\Templates\APAC
Human Resources\Personnel\Templates\EMEA
...
Human Resources\Disputes\IT Services\NA
Human Resources\Disputes\IT Services\SA

一旦我有了上面所有完整路径的数组,就很简单了:

foreach($folder in $MyFolderArray){
    New-Item "\\Server\Share$\$folder" -ItemType Directory -Force
}

问题
我希望能够对任何 CSV 文件执行此操作,无论我有多少列、标题名称是什么,或者每列有多少值。目前,我正在硬编码 4 个 foreach 循环,但该解决方案要求所有 CSV 文件具有相同数量的列和标题名称。我正在寻找可以采用任何列数和长度的任何 CSV 文件的东西。

从任何 CSV 获取所有标题可以这样完成:

$CSVContent = Import-CSV "C:\PathToMyCSVFile"
$CSVHeaders = $CSVContent[0].PSObject.Properties.Name

这可用于将 $CSVContent 拆分为每列一个数组:

for($i=0;$i -lt $CSVHeaders.Count;$i++){
    New-Variable -Name "Header$i" -Value $($CSVContent.$($CSVHeaders[$i]) | Where-Object{$_ -ne ""})
}

这将创建从 $Header0 到 $Header# 的​​数组,其中 # 是 CSV 列数减 1,每个数组都包含该列中的所有值。从这些数组创建具有所有完整路径的最终数组是我卡住的地方。

问题
如何解决构建一个 foreach(foreach(... 循环,枚举所有值组合而不对其进行硬编码的逻辑?我猜这需要递归调用循环本身,但我不知道该怎么做。

【问题讨论】:

    标签: arrays powershell csv


    【解决方案1】:

    对于此类问题,您可以创建recursive function

    维基百科

    在计算机科学中,递归是一种解决问题的方法 其中解决方案取决于较小实例的解决方案 同样的问题。这样的问题一般可以通过迭代来解决,但是 这需要在编程时识别和索引较小的实例 时间。递归通过使用以下函数来解决此类递归问题 从自己的代码中调用自己。该方法可以是 适用于许多类型的问题,递归是核心之一 计算机科学的思想。

    导入数据

    # $Data = Import-Csv .\Data.csv
    # https://www.powershellgallery.com/packages/ConvertFrom-SourceTable
    $Data = ConvertFrom-SourceTable '
    Department       Level1                  Level2                Level3
    Human Resources  Personnel               Templates             APAC
                     Job Applications        Customer Relations    EMEA
                     Salaries and Expenses   Directors             NA
                     Vacation Tracking       Human Resources       SA
                     Disputes                Legal Services
                                             Marketing
                                             Production
                                             Finance
                                             IT Services'
    

    示例

    function Add-Leaves($Path, $i = 0) {
        $Names = $Data[0].PSObject.Properties.Name
        if ($i -lt $Names.count) {
            Foreach ($Leaf in $Data.($Names[$i])) {
                if ($Leaf) { Add-Leaves "$Path\$Leaf" ($i + 1) }
            }
        } else { $Path }
    }
    
    Add-Leaves '\\Server\Share$'
    

    结果

    \\Server\Share$\Human Resources\Personnel\Templates\APAC
    \\Server\Share$\Human Resources\Personnel\Templates\EMEA
    \\Server\Share$\Human Resources\Personnel\Templates\NA
    \\Server\Share$\Human Resources\Personnel\Templates\SA
    \\Server\Share$\Human Resources\Personnel\Customer Relations\APAC
    \\Server\Share$\Human Resources\Personnel\Customer Relations\EMEA
    ...
    

    说明

    1. Add-Leaves($Path, $i = 0)
      也从内部调用的递归函数。 在哪里:
      • $Path 是将添加叶子的当前路径
      • $i 它是列索引,默认:$i = 0(第一列)
    2. $Names = $Data[0].PSObject.Properties.Name
      这将使用称为 member enumeration 的 PowerShell 功能检索所有标头名称(另请参见:Iterate over PSObject properties in PowerShell)。
      请注意,这实际上是一个静态变量,因此可能会放置在类似于静态的函数之外$Data 表(您可以考虑将其作为参数添加到函数中)
    3. if ($i -lt $Names.count) {
      这将检查列索引 ($i) 是否仍在列数内。这意味着这最终将停止递归并防止无限循环(并转到步骤 7.)。
    4. Foreach ($Leaf in $Data.($Names[$i])) {
      这将迭代当前列中的每个值(使用成员枚举)。
      $Names[3] ? 'Level3', $Data.'Level3' ? 'APAC', 'EMEA', 'NA', ...
    5. if ($Leaf) { Add-Leaves "$Path\$Leaf" ($i + 1) }
      • if ($Leaf) { 不包括列中的空字段,例如第一列 Department 只有一个项目 (Human Resources),其余应排除
      • Add-Leaves "$Path\$Leaf" ($i + 1) 是实际的 recursive 调用,其中执行相同的操作:
        • $Path 现在在当前列中包含每个 $Leaf
        • 在下一列($i + 1 = 递归深度)
    6. else { $Path }
      如果所有列都被处理(参见步骤 3.),输出当前的$Path

    待办事项

    如果您真的想创建文件夹,您可能希望在步骤 4.5. 之间执行此操作,并实现类似:

    1. 一个。
      如果当前目录($Path)中不存在子文件夹$Leaf,则创建它("$Path\$Leaf"

    【讨论】:

    • 这很完美,它完全符合我的要求,我不需要创建变量并将它们保存在内存中,非常感谢!我做了一个调整,在函数调用中包含 $data(因为函数也需要 [array]$data),因为我希望它可以在任何地方使用,而不一定首先定义 $data。我猜如果我有太多列,这会使 PS 将大量信息保留在内存中,所以我不确定这是一个很好的权衡。
    • 只有一件事我不完全清楚。当您执行“if ($Leaf) { Add-Leafs...”时,这是在检查 $Leaf 是否为 $null 吗? if($Leaf) 实际上验证了什么?如果我运行 if($null -ne $leaf) 我会得到很多我不想要的条目组合。我认为这里的逻辑是我唯一不明白的地方。
    • 啊,正要对此发表评论:) 我在那里更改了 Foreach 以便不需要 if: Foreach ($Leaf in $($Data.($Names[$i] ) | Where-Object{$_ -ne ""})){... 排除了已经在最顶层 Foreach 中的空字段。再次,谢谢你!这两天一直让我头疼,现在我可以睡了:)
    • 在这光辉的日子里,我们都是学习者!
    • 我能不能说我喜欢你称之为“Add-Leaf”函数的美妙感觉,因为我们正在构建一个文件夹树,非常感谢:)
    猜你喜欢
    • 2020-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多