递归计算子文件夹中的文件数

19

我正在尝试计算目录中所有子文件夹中的文件数,并以列表形式显示它们。

例如,以下目录结构:

TEST
    /VOL01
        file.txt
        file.pic
    /VOL02
        /VOL0201
            file.nu
            /VOL020101
                file.jpg
                file.erp
                file.gif
    /VOL03
        /VOL0301
            file.org

应该输出:

PS> DirX C:\TEST

Directory              Count
----------------------------
VOL01                      2
VOL02                      0
VOL02/VOL0201              1
VOL02/VOL0201/VOL020101    3
VOL03                      0
VOL03/VOL0301              1

我从以下内容开始:

Function DirX($directory)
{
    foreach ($file in Get-ChildItem $directory -Recurse)
    {
        Write-Host $file
    }
}

现在我有一个问题:为什么我的函数没有递归?


你的代码片段并没有按照预期工作,它显示了$directory下面的每个文件。目前的逻辑无法得到你想要的输出。 - Matt
6个回答

43

这样做应该可以起作用:

dir -recurse |  ?{ $_.PSIsContainer } | %{ Write-Host $_.FullName (dir $_.FullName | Measure-Object).Count }
  • dir -recurse列出当前目录下的所有文件并将结果通过管道符(|)传递到
  • ?{ $_.PSIsContainer },该命令仅过滤目录,然后将得到的列表再次传递到
  • %{ Write-Host $_.FullName (dir $_.FullName | Measure-Object).Count },这是一个foreach循环,对于列表中的每个成员($_),显示完整名称和以下表达式的结果
  • (dir $_.FullName | Measure-Object).Count,它提供了 $_.FullName 路径下的文件列表,并通过Measure-Object计数成员

  • ?{ ... }是Where-Object的别名

  • %{ ... }是foreach的别名

这个运行得很好,太棒了。但是你可以把它写得更详细一些吗?对我来说,这个目前还像黑魔法一样。我先尝试理解简单的脚本编写 :-) - Pr0no
感谢您的回答和出色的解释,David。 - BICube
@David 我正在尝试将您的代码适应输出到文件而不是控制台,但我无法想出来。您能否给我一些指导? - BoogieMan2718
1
你可以使用管道符号将输出传递给 Out-File ... | Out-File -FilePath <文件路径>。 - David Brabant
1
如果您想筛选特定的文件(例如,所有以“.txt”结尾的文件),则可以按以下方式修改此命令: dir -recurse | ?{ $_.PSIsContainer } | %{ Write-Host $_.FullName (dir $_.FullName | ?{$_ -match '.txt$'} | Measure-Object).Count } - eddex

9
与David的解决方案类似,这个方案适用于Powershell v3.0,并且不使用别名,以防有人对它们不熟悉。
Get-ChildItem -Directory | ForEach-Object { Write-Host $_.FullName $(Get-ChildItem $_ | Measure-Object).Count}

补充答案

根据关于保持您的函数和循环结构的评论,我提供以下内容。注意我不赞成此解决方案,因为它很丑陋,而内置的 cmdlets 处理这个问题非常好。然而,我还是想帮助你们,所以这是你脚本的更新。

Function DirX($directory)
{
    $output = @{}

    foreach ($singleDirectory in (Get-ChildItem $directory -Recurse -Directory))
    {
        $count = 0 
        foreach($singleFile in Get-ChildItem $singleDirectory.FullName)
        {
            $count++
        }
        $output.Add($singleDirectory.FullName,$count)
    }

    $output | Out-String
}

对于每个 $singleDirectory,使用 $count 统计所有文件的数量(在下一个子循环之前将其重置),并将每个结果输出到哈希表中。最后将哈希表作为字符串输出。在您的问题中,您似乎希望获得对象输出而不是纯文本。

1
@Pr0no,我在我的答案中添加了一些可能更符合您功能的内容。不过请注意,其他提供的答案更多地证明了PowerShell的易用性和强大性。 - Matt
这个方法可以截断长文件路径,而且似乎比我的方法执行速度更快。 - Noah Sparks
稍作调整,因为扩展在运行时使用了错误的路径:Get-ChildItem -Directory | ForEach-Object { Write-Host $_.FullName $(Get-ChildItem $_.FullName | Measure-Object).Count} - David Metcalfe

3

嗯,你现在的做法是整个Get-ChildItem命令需要完成才能开始迭代foreach循环。你确定等待的时间足够长吗?如果你对大型目录(如C:)运行它,那将需要相当长的时间。

编辑:我看到你之前问过如何使你的函数实现你想要的功能,下面是方法。

Function DirX($directory)
{
    foreach ($file in Get-ChildItem $directory -Recurse -Directory )
    {
        [pscustomobject] @{
        'Directory' = $File.FullName
        'Count' = (GCI $File.FullName -Recurse).Count
        }
    }
}
DirX D:\

由于我们只关心目录,因此 foreach 循环仅获取目录。 在循环内部,为每个迭代创建一个自定义对象,并使用文件夹的完整路径和文件夹中项的计数。

另外,请注意,此方法仅适用于 PowerShell 3.0 或更新版本,因为 2.0 中不存在“-directory”参数。


我实际上是在使用原始帖子中提供的测试树来运行它。目前,该函数仅显示直接的子节点:VOL01VOL02VOL03 - Pr0no
1
我在2.0和4.0中都进行了测试,以确保它不是版本问题,并且在两个版本中都给出了完整的结果。 - Noah Sparks
抱歉,我的错误。我是PowerShell的新手(在PowerShell ISE中工作),必须刷新我的脚本才能看到结果的更改。我以为仅保存脚本就足够了。现在我可以获取包括子文件夹和文件在内的完整列表。如何列出每个文件夹中的文件数量? - Pr0no
没问题,很高兴你能找出这个问题。 - Noah Sparks
@NoahSparks 考虑将 $file 更改为其他名称。它代表一个目录而不是文件。尽管这不会影响脚本的功能。+1 因为您处理对象创建比我做得更好。 - Matt
谢谢,好的观点。我只是在基于原帖上进行构建,并没有过多关注。 - Noah Sparks

1
Get-ChildItem $rootFolder `
    -Recurse -Directory | 
        Select-Object `
            FullName, `
            @{Name="FileCount";Expression={(Get-ChildItem $_ -File | 
        Measure-Object).Count }} 

最好的建议是去掉反引号,并使用自然行连续符进行格式化 - 请参阅https://get-powershellblog.blogspot.com/2017/07/bye-bye-backtick-natural-line.html。 - pavol.kutaj

0

我的版本-稍微简洁,可以将内容转储到文件中

原始版 - 递归计算子文件夹中的文件数

第二个组件 - 使用PowerShell计算文件夹中的项目数

$FOLDER_ROOT = "F:\"
$OUTPUT_LOCATION = "F:DLS\OUT.txt"
Function DirX($directory)
{
    Remove-Item $OUTPUT_LOCATION

    foreach ($singleDirectory in (Get-ChildItem $directory -Recurse -Directory))
    {
        $count = Get-ChildItem $singleDirectory.FullName -File | Measure-Object | %{$_.Count}
        $summary = $singleDirectory.FullName+"    "+$count+"    "+$singleDirectory.LastAccessTime
        Add-Content $OUTPUT_LOCATION $summary
    }
}
DirX($FOLDER_ROOT)

0

我稍微修改了David Brabant的解决方案,以便我可以评估结果:

$FileCounter=gci "$BaseDir" -recurse |  ?{ $_.PSIsContainer } | %{ (gci "$($_.FullName)" | Measure-Object).Count }
Write-Host "File Count=$FileCounter"
If($FileCounter -gt 0) {
  ... take some action...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接