Powershell 从 CSV 文件中的所有列生成所有可能组合的数组



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 ResourcesPersonnelTemplatesAPAC
Human ResourcesPersonnelTemplatesEMEA
...
Human ResourcesDisputesIT ServicesNA
Human ResourcesDisputesIT ServicesSA

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

foreach($folder in $MyFolderArray){
New-Item "\ServerShare$$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 ""})
}

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

Question
如何解决构建foreach(foreach(...枚举所有值组合而不对此进行硬编码的循环?我猜这需要递归调用循环本身,但我不确定该怎么做。

对于此类问题,您可以创建一个递归函数。

维基百科

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

导入数据

# $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 '\ServerShare$'

结果

\ServerShare$Human ResourcesPersonnelTemplatesAPAC
\ServerShare$Human ResourcesPersonnelTemplatesEMEA
\ServerShare$Human ResourcesPersonnelTemplatesNA
\ServerShare$Human ResourcesPersonnelTemplatesSA
\ServerShare$Human ResourcesPersonnelCustomer RelationsAPAC
\ServerShare$Human ResourcesPersonnelCustomer RelationsEMEA
...

解释

  1. Add-Leaves($Path, $i = 0)
    递归函数,也从内部调用。 哪里:
    • $Path是叶子将添加到的当前路径
    • $i列为列索引,默认值:$i = 0(第一列)
  2. $Names = $Data[0].PSObject.Properties.Name
    这将使用称为成员枚举的 PowerShell 功能检索所有标头名称(另请参阅:循环访问 PowerShell 中的 PSObject 属性).
    请注意,这实际上是一个静态变量,因此可能放置在函数外部,类似于静态$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)是实际的递归调用,其中执行相同的操作:
      • $Path现在包括当前列中的每个$Leaf
      • 在下一列($i + 1= 递归深度)
  6. else { $Path }
    如果所有列都已处理(请参阅步骤 3.),则输出当前$Path

待办事项

如果您确实想要创建文件夹,您可能希望在步骤4.5.之间执行此操作,并实现如下内容:

  1. a.
    如果当前目录中不存在子文件夹$Leaf($Path),请创建它 ("$Path$Leaf")

最新更新