如何以递归方式删除PowerShell中的所有空文件夹?

时间:2015-02-20 14:34:12

标签: powershell window

我需要以递归方式删除PowerShell中特定文件夹的所有空文件夹(任何级别的文件夹和子文件夹)。

目前我正在使用此脚本但没有成功。

你能告诉我如何解决它吗?

$tdc='C:\a\c\d\'
$a = Get-ChildItem $tdc -recurse | Where-Object {$_.PSIsContainer -eq $True}
$a | Where-Object {$_.GetFiles().Count -eq 0} | Select-Object FullName

我在Win 8.1上使用PowerShell

14 个答案:

答案 0 :(得分:32)

在查看这样的问题时,您需要记住一些关键事项:

  1. Get-ChildItem -Recurse执行头递归,这意味着它在遍历树时找到它们时立即返回文件夹。由于您要删除空文件夹,并且在删除空文件夹后删除它们的父项(如果它们为空),则需要使用尾递归,它将处理从最深的子项到根的文件夹。通过使用尾递归,不需要重复调​​用删除空文件夹的代码 - 一个调用将为您完成所有操作。
  2. 默认情况下,Get-ChildItem不会返回隐藏文件或文件夹。因此,您需要采取额外步骤以确保不删除显示为空但包含隐藏文件或文件夹的文件夹。 Get-Item和Get-ChildItem都有一个-Force参数,可用于检索隐藏文件或文件夹以及可见文件或文件夹。
  3. 考虑到这些要点,这里有一个使用尾递归并正确跟踪隐藏文件或文件夹的解决方案,确保删除隐藏文件夹(如果它们为空)并确保保留可能包含一个或多个隐藏文件夹的文件夹文件:

    # First create some test data under C:\a (make sure this is not
    # a directory you care about, because this will remove it if it
    # exists). This test data contains a directory that is hidden
    # that should be removed as well as a file that is hidden in a
    # directory that should not be removed.
    Remove-Item -Force -Path C:\a -Recurse
    New-Item -Force -Path C:\a\b\c\d -ItemType Directory > $null
    $hiddenFolder = Get-Item -Force -LiteralPath C:\a\b\c
    $hiddenFolder.Attributes = $hiddenFolder.Attributes -bor [System.IO.FileAttributes]::Hidden
    New-Item -Force -Path C:\a\b\e -ItemType Directory > $null
    New-Item -Force -Path C:\a\f -ItemType Directory > $null
    New-Item -Force -Path C:\a\f\g -ItemType Directory > $null
    New-Item -Force -Path C:\a\f\h -ItemType Directory > $null
    Out-File -Force -FilePath C:\a\f\test.txt -InputObject 'Dummy file'
    Out-File -Force -FilePath C:\a\f\h\hidden.txt -InputObject 'Hidden file'
    $hiddenFile = Get-Item -Force -LiteralPath C:\a\f\h\hidden.txt
    $hiddenFile.Attributes = $hiddenFile.Attributes -bor [System.IO.FileAttributes]::Hidden
    
    # Now define a script block that will remove empty folders under
    # a root folder, using tail-recursion to ensure that it only
    # walks the folder tree once. -Force is used to be able to process
    # hidden files/folders as well.
    $tailRecursion = {
        param(
            $Path
        )
        foreach ($childDirectory in Get-ChildItem -Force -LiteralPath $Path -Directory) {
            & $tailRecursion -Path $childDirectory.FullName
        }
        $currentChildren = Get-ChildItem -Force -LiteralPath $Path
        $isEmpty = $currentChildren -eq $null
        if ($isEmpty) {
            Write-Verbose "Removing empty folder at path '${Path}'." -Verbose
            Remove-Item -Force -LiteralPath $Path
        }
    }
    
    # Lastly invoke the script block and pass in a root path where
    # you want it to start. This will remove all empty folders in
    # the folder you specify, including empty folders that contain
    # nothing but empty folders, including the start folder if that 
    # winds up as empty.
    & $tailRecursion -Path 'C:\a'
    

答案 1 :(得分:27)

您可以使用:

$tdc="C:\a\c\d"
$dirs = gci $tdc -directory -recurse | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName
$dirs | Foreach-Object { Remove-Item $_ }

$dirs将是过滤后从Get-ChildItem命令返回的空目录数组。然后,您可以循环覆盖它以删除项目。

更新

如果要删除包含空目录的目录,只需继续运行脚本,直到它们全部消失。您可以循环直到$dirs为空:

$tdc="C:\a\c\d"
do {
  $dirs = gci $tdc -directory -recurse | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName
  $dirs | Foreach-Object { Remove-Item $_ }
} while ($dirs.count -gt 0)

如果您想确保隐藏文件和文件夹也会被删除,请添加-Force标记:

do {
  $dirs = gci $tdc -directory -recurse | Where { (gci $_.fullName -Force).count -eq 0 } | select -expandproperty FullName
  $dirs | Foreach-Object { Remove-Item $_ }
} while ($dirs.count -gt 0)

答案 2 :(得分:4)

ls c:\temp -rec |%{ if ($_.PSIsContainer -eq $True) {if ( (ls $_.fullname -rec | measure |select -expand count ) -eq "0"  ){ ri $_.fullname -whatif}  }  }  

答案 3 :(得分:1)

假设您在感兴趣的父文件夹中

gci . -Recurse -Directory | % { if(!(gci -Path $_.FullName)) {ri -Force -Recurse $_.FullName} }

对于$tdc的情况,它将是

gci $tdc -Recurse -Directory | % { if(!(gci -Path $_.FullName)) {ri -Force -Recurse $_.FullName} }

答案 4 :(得分:1)

我想我会为这里已经很长的答案做出贡献。

许多答案都有些古怪,例如需要运行多次。其他的对于普通用户来说过于复杂(例如使用尾部递归来防止重复扫描等)。

这是我使用多年的非常简单的单线纸,并且效果很好...

它不考虑隐藏的文件/文件夹,但是您可以通过在-Force命令中添加Get-ChildItem来解决此问题

这是标准的长cmdlet名称版本:

Get-ChildItem -Recurse -Directory | ? {-Not $_.EnumerateFiles('*',1) | Select-Object -First 1} | Remove-Item -Recurse

基本上...这是怎么回事:

  • Get-ChildItem -Recurse -Directory-开始递归扫描以查找目录
  • $_.EnumerateFiles('*',1)-对于每个目录...枚举文件
    • EnumerateFiles会一直输出其结果,GetFiles将在完成后输出...。至少,由于某种原因,它应该在.NET中工作……在PowerShell GetFiles中立即开始吐出。但是我仍然使用EnumerateFiles,因为在测试中它确实更快。
    • ('*',1)意味着递归查找所有文件。
  • | Select-Object -First 1-在找到的第一个文件处停止
    • 很难测试它的帮助程度。在某些情况下,它起到了很大的帮助,而在另一些情况下,它根本没有帮助,在某些情况下,它使速度降低了一小步。所以我真的不知道。我想这是可选的。
  • | Remove-Item -Recurse-递归删除目录(确保删除包含空子目录的目录)

如果您要计算字符,则可以简化为:

ls -s -ad | ? {-Not $_.EnumerateFiles('*',1) | select -First 1} | rm -Recurse
  • -s--Recurse的别名
  • -ad--Directory的别名

如果您真的不关心性能是因为您没有那么多文件...。甚至更是如此:

ls -s -ad | ? {!($_.GetFiles('*',1))} | rm -Recurse

旁注: 在解决这个问题时,我开始在具有数百万个文件和数千个目录的服务器上使用Measure-Command测试各种版本。

这比我一直在使用(上面)的命令快:

(gi .).EnumerateDirectories('*',1) | ? {-Not $_.EnumerateFiles('*',1) } | rm -Recurse

答案 5 :(得分:0)

递归删除空子目录也可以使用“For循环”来完成。

在我们开始之前,让我们制作一些子目录&在$ HOME \ Desktop \ Test

中使用的文本文件
MD $HOME\Desktop\Test\0\1\2\3\4\5 
MD $HOME\Desktop\Test\A\B\C\D\E\F
MD $HOME\Desktop\Test\A\B\C\DD\EE\FF
MD $HOME\Desktop\Test\Q\W\E\R\T\Y
MD $HOME\Desktop\Test\Q\W\E\RR
"Hello World" > $HOME\Desktop\Test\0\1\Text1.txt
"Hello World" > $HOME\Desktop\Test\A\B\C\D\E\Text2.txt
"Hello World" > $HOME\Desktop\Test\A\B\C\DD\Text3.txt
"Hello World" > $HOME\Desktop\Test\Q\W\E\RR\Text4.txt

首先,将以下脚本块存储在变量$ SB中。稍后可以使用& SB命令调用变量。 & SB命令将输出$ HOME \ Desktop \ Test

中包含的空子目录列表
$SB = {
    Get-ChildItem $HOME\Desktop\Test -Directory -Recurse |
    Where-Object {(Get-ChildItem $_.FullName -Force).Count -eq 0}
}

注意:-Force参数非常重要。它确保包含隐藏文件和子目录但在其他方面为空的目录不会在“For循环”中删除。

现在使用“For循环”以递归方式删除$ HOME \ Desktop \ Test中的空子目录

For ($Empty = &$SB ; $Empty -ne $null ; $Empty = &$SB) {Remove-Item (&$SB).FullName}

在PowerShell 4.0上进行测试

答案 6 :(得分:0)

如果您只是想确保只删除可能包含子文件夹但本身及其子文件夹中没有文件的文件夹,这可能会更容易,更快捷。

$Empty = Get-ChildItem $Folder -Directory -Recurse |
Where-Object {(Get-ChildItem $_.FullName -File -Recurse -Force).Count -eq 0}

Foreach ($Dir in $Empty)
{
    if (test-path $Dir.FullName)
    {Remove-Item -LiteralPath $Dir.FullName -recurse -force}
}

答案 7 :(得分:0)

我改编了RichardHowells的剧本。 如果存在thumbs.db,则不会删除该文件夹。

##############
# Parameters #
##############
param(
    $Chemin = "" ,  # Path to clean
    $log = ""       # Logs path
)




###########
# Process #
###########


if (($Chemin -eq "") -or ($log-eq "") ){

    Write-Error 'Parametres non reseignes - utiliser la syntaxe : -Chemin "Argument"  -log "argument 2" ' -Verbose 
    Exit
}



#loging 
$date = get-date -format g
Write-Output "begining of cleaning folder : $chemin at $date" >> $log
Write-Output "------------------------------------------------------------------------------------------------------------" >> $log


<########################################################################
    define a script block that will remove empty folders under a root folder, 
    using tail-recursion to ensure that it only walks the folder tree once. 
    -Force is used to be able to process hidden files/folders as well.
########################################################################>
$tailRecursion = {
    param(
        $Path
    )
    foreach ($childDirectory in Get-ChildItem -Force -LiteralPath $Path -Directory) {
        & $tailRecursion -Path $childDirectory.FullName
    }
    $currentChildren = Get-ChildItem -Force -LiteralPath $Path
    Write-Output $childDirectory.FullName



    <# Suppression des fichiers Thumbs.db #>
    Foreach ( $file in $currentchildren )
    {
        if ($file.name -notmatch "Thumbs.db"){break}
        if ($file.name -match "Thumbs.db"){
            Remove-item -force -LiteralPath $file.FullName}

    }



    $currentChildren = Get-ChildItem -Force -LiteralPath $Path
    $isEmpty = $currentChildren -eq $null
    if ($isEmpty) {
        $date = get-date -format g
        Write-Output "Removing empty folder at path '${Path}'.  $date" >> $log
        Remove-Item -Force -LiteralPath $Path
    }
}

# Invocation of the script block
& $tailRecursion -Path $Chemin

#loging 
$date = get-date -format g
Write-Output "End of cleaning folder : $chemin at $date" >> $log
Write-Output "------------------------------------------------------------------------------------------------------------" >> $log

答案 8 :(得分:0)

除非您也想删除嵌套超过一个文件夹的文件,否则我不会对第1条评论置若heart闻。您最终将删除可能包含可能包含文件的目录的目录。更好:

$ FP =“” C:\ Temp \“

$ dirs = Get-Childitem -LiteralPath $ FP -directory -recurse

$ Empty = $ dirs |哪里对象{$ .GetFiles()。Count -eq 0 -和 $ .GetDirectories()。Count -eq 0} |

选择对象全名

以上检查以确保目录实际上为空,而OP仅检查以确保没有文件。反过来,这将导致旁边几个文件夹中的文件也被删除。

您可能需要多次运行上面的命令,因为它不会删除嵌套了Dirs的Dirs。因此,它仅删除最深的级别。因此循环播放,直到它们全部消失。

我不做的是使用-force参数。那是设计使然。如果实际上remove-item命中的目录不为空,则需要提示您以提高安全性。

答案 9 :(得分:0)

Get-ChildItem $tdc -Recurse -Force -Directory | 
    Sort-Object -Property FullName -Descending |
    Where-Object { $($_ | Get-ChildItem -Force | Select-Object -First 1).Count -eq 0 } |
    Remove-Item -Verbose

这里唯一的新颖贡献是使用Sort-Object对目录FullName进行反向排序。这将确保我们在处理父母之前总是先处理孩子。这样就可以递归地删除空文件夹。

暂时,我不确定Select-Object -First 1是否会有意义地提高性能,但可能会。

答案 10 :(得分:0)

类似的事情对我有用。该脚本删除空文件夹和仅包含文件夹的文件夹(无文件,无隐藏文件)。

$items = gci -LiteralPath E:\ -Directory -Recurse
$dirs = [System.Collections.Generic.HashSet[string]]::new([string[]]($items |% FullName))
for (;;) {
    $remove = $dirs |? { (gci -LiteralPath $_ -Force).Count -eq 0 }
    if ($remove) {
        $remove | rm
        $dirs.ExceptWith( [string[]]$remove )
    }
    else {
        break
    }
}

答案 11 :(得分:0)

colors.dart

$files = Get-ChildItem -Path c:\temp -Recurse -Force | where psiscontainer ; [array]::reverse($files) 将撤消您的项目,因此您将首先获得层次结构中最低的文件。 在删除文件路径之前,我用它来处理文件路径过长的文件名。

答案 12 :(得分:0)

这是一种简单的方法

dir -Directory | ? { (dir $_).Count -eq 0 } | Remove-Item

答案 13 :(得分:0)

这将删除指定目录 $tdc 中的所有空文件夹。 由于不需要多次运行,因此速度也快了很多。

    $tdc = "x:\myfolder" # Specify the root folder
    gci $tdc -Directory -Recurse `
        | Sort-Object { $_.FullName.Length } -Descending `
        | ? { $_.GetFiles().Count -eq 0 } `
        | % {
            if ($_.GetDirectories().Count -eq 0) { 
                Write-Host " Removing $($_.FullName)"
                $_.Delete()
                }
            }