【问题标题】:Powershell, Loop through CSV files and search for a string in a row, then ExportPowershell,遍历CSV文件并连续搜索字符串,然后导出
【发布时间】:2026-01-17 04:15:02
【问题描述】:

我在服务器上有一个名为“servername”的目录。在该目录中,我有名称为日期的子目录。在这些日期目录中,我有大约 150 个 .csv 文件审核日志。 我有一个部分工作的脚本,它从日期目录内部开始,枚举并循环遍历 .csv 并在列中搜索字符串。我试图让它导出每个匹配的行,然后继续下一个文件。

$files = Get-ChildItem '\\servername\volume\dir1\audit\serverbeingaudited\20180525'

ForEach ($file in $files) {
    $Result = If (import-csv $file.FullName | Where {$_.'path/from' -like "*01May18.xlsx*"})
    {
    $result | Export-CSV -Path c:\temp\output.csv -Append}
    }

我正在做的是在 'path\from' 列中搜索字符串 - 例如文件名。该列包含的数据总是某种形式的 \folder\folder\folder\filename.xls。我正在搜索特定文件名以及该文件中该列中该文件名的所有实例。

我的问题是导出该行 - export.csv 始终为空。我还想启动一个目录'up'并遍历每个日期目录,解析,导出,然后继续下一个目录和文件。

如果我将其分解为一个文件并将其从 IF 中取出,它似乎会给我一个结果,所以我认为我在 IF 或 For-each 中遇到了问题,但显然这超出了我的工资等级 - 无法弄清楚出来……

提前感谢您的任何帮助, 理查德X

【问题讨论】:

    标签: powershell csv


    【解决方案1】:

    问题在于您的 If 块,当您说 $Result = If () {$Result | ...} 时,您是在说新的 $Result 等于 if 语句返回的内容。由于尚未定义 $Result,因此这是 $Result = If () {$null | ...},这就是您得到空行的原因。

    甚至不需要If 块。您已经使用Where-Object 过滤了您的csv,只需继续将这些对象沿管道传递到导出。

    因为听起来你只是对父级的所有子文件夹运行这个,听起来你可以只使用 Get-ChildItem-Recurse 参数

    Get-ChildItem '\\servername\volume\dir1\audit\serverbeingaudited\' -Recurse |
        ForEach-Object {
            Import-csv $_.FullName |
                Where-Object {$_.'path/from' -like "*01May18.xlsx*"} 
        } | Export-CSV -Path c:\temp\output.csv 
    

    (我使用ForEach-Object 循环而不是foreach 只是演示了对象以另一种方式沿管道传递)

    编辑:根据 Bill_Stewart 的建议删除了附加信息。将为运行中的递归文件夹写出所有条目。将在下次运行时覆盖。

    【讨论】:

    • 没有必要在循环中使用Export-Csv -Append(事实上,它可能相当低效,具体取决于它执行的次数)。只需将输出通过管道传输到最后的Export-Csv(请参阅我的答案)。
    • @Bill_Stewart 这是一个很好的建议,但需要注意的是每次新运行都会覆盖文件(这通常是所需的行为,而不是重复的条目)。不确定预期的行为是什么。通过在最后执行文件写入也可以更有效,其代价是如果脚本在之前的操作中途失败,则不会被记录。有很多优点/缺点的选择。
    • 为防止追加,请将您的Export-Csv 移动到管道的最末端( ForEach-Object 脚本块之后,而不是 中) .
    • @Bill_Stewart 这就是我想说的以前的操作不会被记录。 只是还没来得及更新。
    • 是的,这就是我想说的——现在你的答案和我的一样(-Recurse-NoTypeInformation 除外)。
    【解决方案2】:

    我认为不需要附加 CSV 文件?怎么样:

    Get-ChildItem '\\servername\volume\dir1\audit\serverbeingaudited\20180525' | ForEach-Object {
      Import-Csv $_.FullName | Where-Object { $_.'path/from' -like '*01May18.xlsx*' }
    } | Export-Csv 'C:\Temp\Output.csv' -NoTypeInformation
    

    【讨论】:

    • 谢谢比尔 - '追加'是因为我希望从 100 个左右的文件中搜索字符串的每个匹配项都将解析到一个文件中。不过,我没有考虑到的一件事是,这些日志中的时间戳只是部分的——日志文件的名称类似于 audit-15-19-28,当您打开日志文件时,时间戳只是 19:28。 xx 所以通过附加每场比赛我无法弄清楚实际时间是多少。我有一个想法如何解决它,但我们会看到。如果需要,我会为此打开另一个问题。
    • 这只会创建一个文件。
    • 我对 export-csv 的理解是它会替换每次运行的文件内容,这是不可取的。我想要一个文件,每个匹配项都添加到文件末尾。
    • 只有在循环内。请注意,我的代码仅在管道结束时导出。 (试试看。)
    • 正确 - 没有注意到代码窗口中的结束脚本块。现在试试这个方法。敬请期待...
    【解决方案3】:

    假设您的 CSV 格式相同,并且您的搜索文本不太可能出现在任何其他列中,您可以使用 Select-String 而不是 Import-Csv。因此,您无需将字符串转换为对象并再次转换回字符串,而是可以将其作为字符串进行处理。您需要添加额外的行来伪造标题行,如下所示:

    $files = Get-ChildItem '\\servername\volume\dir1\audit\serverbeingaudited\20180525'
    
    $result = @()
    $result += Get-Content $files[0] -TotalCount 1 
    
    $result += ($files | Select-String -Pattern '01May18\.xlsx').Line 
    $result | Out-File 'c:\temp\output.csv'
    

    【讨论】: