【问题标题】:PowerShell: Find and delete folders that have no files in them or in child foldersPowerShell:查找和删除其中没有文件或子文件夹中的文件夹
【发布时间】:2026-02-10 18:10:01
【问题描述】:

我有一个 PowerShell 2.0 脚本,用于删除其中没有文件的文件夹:

dir 'P:\path\to\wherever' -recurse | Where-Object { $_.PSIsContainer } | Where-Object { $_.GetFiles().Count -eq 0 } | foreach-object { remove-item $_.fullname -recurse}

但是,我注意到运行脚本时出现大量错误。即:

Remove-Item : Directory P:\path\to\wherever cannot be removed because it is not empty.

“什么?!”我惊慌失措。它们应该全部为空!我只过滤空文件夹!显然这不是脚本的工作方式。在这种情况下,只有文件夹作为子文件夹,而文件作为孙文件夹的文件夹被认为是空的:

Folder1 (no files - 1 folder) \ Folder 2 (one file)

在这种情况下,PowerShell 将 Folder1 视为空并尝试将其删除。这让我感到困惑的原因是,如果我在 Windows 资源管理器中右键单击 Folder1,它会说 Folder1 中有 1 个文件夹和 1 个文件。无论是用于从 Explorer 中计算 Folder1 下的子对象,它都可以无限地查看孙对象。

问题:

如果我的脚本有文件作为孙子或以后的文件,我怎样才能让我的脚本不认为文件夹为空?

【问题讨论】:

    标签: windows powershell powershell-2.0


    【解决方案1】:

    这是我在最近的脚本中使用的递归函数...

    function DeleteEmptyDirectories {
      param([string] $root)
    
      [System.IO.Directory]::GetDirectories("$root") |
        % {
          DeleteEmptyDirectories "$_";
          if ([System.IO.Directory]::GetFileSystemEntries("$_").Length -eq 0) {
            Write-Output "Removing $_";
            Remove-Item -Force "$_";
          }
        };
    }
    
    DeleteEmptyDirectories "P:\Path\to\wherever";
    

    【讨论】:

      【解决方案2】:

      更新递归删除:

      您可以使用如下嵌套管道:

      dir -recurse | Where {$_.PSIsContainer -and `
      @(dir -Lit $_.Fullname -r | Where {!$_.PSIsContainer}).Length -eq 0} |
      Remove-Item -recurse -whatif
      

      (从这里 - How to delete empty subfolders with PowerShell?


      也添加一个($_.GetDirectories().Count -eq 0) 条件:

      dir path -recurse | Where-Object { $_.PSIsContainer } | Where-Object { ($_.GetFiles().Count -eq 0) -and ($_.GetDirectories().Count -eq 0) } | Remove-Item
      

      这里有一个更简洁的方法:

      dir path -recurse | where {!@(dir -force $_.fullname)} | rm -whatif
      

      请注意,您在删除项目时不需要Foreach-Object。还将-whatif 添加到Remove-Item 以查看它是否会按照您的预期进行。

      【讨论】:

      • 啊哈!那讲得通。但是,我必须一遍又一遍地运行脚本才能真正清除所有空的东西,感觉第一次运行会得到树上最远的叶子。然后第二个将获得自上次运行脚本以来现在为空的下一组。等等。那么我是循环遍历脚本直到没有输出还是有更优雅的方法呢?您更愿意将其作为一个完全独立的问题吗?
      • @wesley - 您可以使用递归函数。检查我的答案。
      • @WesleyDavid - 您可以嵌套管道。查看我更新的答案和链接的答案。
      • @WesleyDavid - 我同意 Dave Cluderay 的递归函数确实更容易理解。
      • 现在正在处理它。我很难理解 @(dir -Lit $_.Fullname -r | Where {!$_.PSIsContainer}) 部分,但我会继续研究它一段时间。
      【解决方案3】:

      制作这个脚本时出现了一些问题,其中之一是使用它来检查文件夹是否为空:

      {!$_.PSIsContainer}).Length -eq 0
      

      但是,我发现空文件夹的大小不是 0 而是 NULL。以下是我将使用的 PowerShell 脚本。它是不是我自己的。相反,它来自 PowerShell MVP Richard Siddaway。你可以在this thread on PowerShell.com看到这个函数来自的线程。

      function remove-emptyfolder {
       param ($folder)
      
       foreach ($subfolder in $folder.SubFolders){
      
       $notempty = $false
       if (($subfolder.Files | Measure-Object).Count -gt 0){$notempty = $true}
       if (($subFolders.SubFolders  | Measure-Object).Count -gt 0){$notempty = $true}
       if ($subfolder.Size -eq 0 -and !$notempty){
         Remove-Item -Path $($subfolder.Path) -Force -WhatIf
       }
       else {
        remove-emptyfolder $subfolder
       }
      
      }
      
      }
      
      $path = "c:\test"
      $fso = New-Object -ComObject "Scripting.FileSystemObject"
      
      $folder = $fso.GetFolder($path)
      remove-emptyfolder $folder
      

      【讨论】:

        【解决方案4】:

        您可以为此使用递归函数。其实我已经写过一篇了:

        cls
        
        $dir = "C:\MyFolder"
        
        Function RecurseDelete()
        {
            param   (
                    [string]$MyDir
                    )
        
            IF (!(Get-ChildItem -Recurse $mydir | Where-Object {$_.length -ne $null}))
                {
                    Write-Host "Deleting $mydir"
                    Remove-Item -Recurse $mydir
                }
            ELSEIF (Get-ChildItem $mydir | Where-Object {$_.length -eq $null})
                {
                    ForEach ($sub in (Get-ChildItem $mydir | Where-Object {$_.length -eq $null}))
                    {
                        Write-Host "Checking $($sub.fullname)"
                        RecurseDelete $sub.fullname
                    }   
                }
            ELSE
                {
                    IF (!(Get-ChildItem $mydir))
                        {
                            Write-Host "Deleting $mydir"
                            Remove-Item $mydir
                        }
        
                }
        }
        
        IF (Test-Path $dir) {RecurseDelete $dir}
        

        【讨论】:

        • +1 您的解决方案有效,但我想提出一项改进:当文件夹名称包含方括号时,Remove-Item 调用会失败(静默)。修复很简单;像这样添加-LiteralPath 参数:Remove-Item -Recurse -LiteralPath $mydirRemove-Item -LiteralPath $mydir。谢谢。