简体   繁体   English

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

[英]How to recursively remove all empty folders in PowerShell?

I need to recursively remove all empty folders for a specific folder in PowerShell (checking folder and sub-folder at any level).我需要递归删除 PowerShell 中特定文件夹的所有空文件夹(检查任何级别的文件夹和子文件夹)。

At the moment I am using this script with no success.目前我使用这个脚本没有成功。

Could you please tell me how to fix it?你能告诉我如何解决吗?

$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

I am using PowerShell on Windows 8.1 version.我在 Windows 8.1 版本上使用 PowerShell。

You need to keep a few key things in mind when looking at a problem like this:在查看这样的问题时,您需要牢记一些关键事项:

  1. Get-ChildItem -Recurse performs head recursion, meaning it returns folders as soon as it finds them when walking through a tree. Get-ChildItem -Recurse执行头递归,这意味着它在遍历树时一旦找到文件夹就返回文件夹。 Since you want to remove empty folders, and also remove their parent if they are empty after you remove the empty folders, you need to use tail recursion instead, which processes the folders from the deepest child up to the root.由于您要删除空文件夹,并且在删除空文件夹后如果它们为空,还要删除其父文件夹,因此您需要改用尾递归,它处理从最深子文件夹到根文件夹的文件夹。 By using tail recursion, there will be no need for repeated calls to the code that removes the empty folders -- one call will do it all for you.通过使用尾递归,无需重复调用删除空文件夹的代码——一次调用即可为您完成所有工作。
  2. Get-ChildItem does not return hidden files or folders by default.默认情况下, Get-ChildItem不返回隐藏文件或文件夹。 As a result you need to take extra steps to ensure that you don't remove folders that appear empty but that contain hidden files or folders.因此,您需要采取额外的步骤来确保您不会删除看起来为空但包含隐藏文件或文件夹的文件夹。 Get-Item and Get-ChildItem both have a -Force parameter which can be used to retrieve hidden files or folders as well as visible files or folders. Get-ItemGet-ChildItem都有一个-Force参数,可用于检索隐藏文件或文件夹以及可见文件或文件夹。

With those points in mind, here is a solution that uses tail recursion and that properly tracks hidden files or folders, making sure to remove hidden folders if they are empty and also making sure to keep folders that may contain one or more hidden files.考虑到这些要点,这里是一种使用尾递归并正确跟踪隐藏文件或文件夹的解决方案,确保删除为空的隐藏文件夹,并确保保留可能包含一个或多个隐藏文件的文件夹。

First this is the script block (anonymous function) that does the job:首先这是完成工作的脚本块(匿名函数):

# A script block (anonymous function) 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
    }
}

If you want to test it here's code that will create interesting test data ( make sure you don't already have a folder c:\\a because it will be deleted ):如果你想测试它,这里的代码将创建有趣的测试数据(确保你还没有文件夹c:\\a因为它将被删除):

# This creates 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

Here's how you use it.这是你如何使用它。 Note that this will remove the top folder (the C:\\a folder in this example, which gets created if you generated the test data using the script above) if that folder winds up being empty after deleting all empty folders under it.请注意,如果该文件夹在删除其下的所有空文件夹后最终为空,这将删除顶部文件夹(本示例中的C:\\a文件夹,如果您使用上面的脚本生成测试数据,则会创建该文件夹)。

& $tailRecursion -Path 'C:\a'

You can use this:你可以使用这个:

$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 will be an array of empty directories returned from the Get-ChildItem command after filtering. $dirs将是过滤后从Get-ChildItem命令返回的空目录数组。 You can then loop over it to remove the items.然后,您可以遍历它以删除项目。

Update更新

If you want to remove directories that contain empty directories, you just need to keep running the script until they're all gone.如果您想删除包含空目录的目录,您只需要继续运行脚本,直到它们全部消失。 You can loop until $dirs is empty:您可以循环直到$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)

If you want to ensure that hidden files and folders will also be removed, include the -Force flag:如果要确保也将删除隐藏的文件和文件夹,请包含-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)
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

The only novel contribution here is using Sort-Object to reverse sort by the directory's FullName.这里唯一的新颖贡献是使用Sort-Object按目录的 FullName 进行反向排序。 This will ensure that we always process children before we process parents (ie, "tail recursion" as described by Kirk Munro's answer).这将确保我们始终在处理父项之前处理子项(即 Kirk Munro 的回答所描述的“尾递归”)。 That makes it recursively remove empty folders.这使得它递归地删除空文件夹。

Off hand, I'm not sure if the Select-Object -First 1 will meaningfully improve performance or not, but it may. Select-Object -First 1 ,我不确定Select-Object -First 1是否会有意义地提高性能,但可能会。

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

Assuming you're inside the parent folder of interest假设您在感兴趣的父文件夹中

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

For your case with $tdc it'll be对于$tdc的情况,它将是

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

If you just want to make sure, that you delete only folders that may contain subfolders but no files within itself and its subfolders, this may be an easier an quicker way.如果您只是想确保只删除可能包含子文件夹但不删除其本身及其子文件夹中的文件的文件夹,这可能是一种更简单、更快捷的方法。

$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}
}

Just figured I would contribute to the already long list of answers here.只是想我会在这里为已经很长的答案列表做出贡献。

Many of the answers have quirks to them, like needing to run more than once.许多答案都有其怪癖,比如需要运行不止一次。 Others are overly complex for the average user (like using tail recursion to prevent duplicate scans, etc).其他的对于普通用户来说过于复杂(比如使用尾递归来防止重复扫描等)。

Here is a very simple one-liner that I've been using for years, and works great...这是我多年来一直使用的一个非常简单的单线,并且效果很好......

It does not account for hidden files/folders, but you can fix that by adding -Force to the Get-ChildItem command它不考虑隐藏文件/文件夹,但您可以通过将-Force添加到Get-ChildItem命令来解决该问题

This is the long, fully qualified cmdlet name version:这是长的、完全限定的 cmdlet 名称版本:

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

So basically...here's how it goes:所以基本上......这是它的过程:

  • Get-ChildItem -Recurse -Directory - Start scanning recursively looking for directories Get-ChildItem -Recurse -Directory - 开始递归扫描查找目录
  • $_.EnumerateFiles('*',1) - For each directory...Enumerate the files $_.EnumerateFiles('*',1) - 对于每个目录...枚举文件
    • EnumerateFiles will output its findings as it goes, GetFiles will output when it is done....at least, that's how it is supposed to work in .NET...for some reason in PowerShell GetFiles starts spitting out immediately. EnumerateFiles将输出它的结果, GetFiles将在它完成时输出......至少,这就是它应该在 .NET 中工作的方式......出于某种原因,在 PowerShell 中GetFiles立即开始吐出。 But I still use EnumerateFiles because in testing it was reliably faster.但我仍然使用EnumerateFiles因为在测试中它可靠得更快。
    • ('*',1) means find ALL files recursively. ('*',1)表示递归查找所有文件。
  • | Select-Object -First 1 | Select-Object -First 1 - Stop at the first file found | Select-Object -First 1 - 在找到的第一个文件处停止
    • This was difficult to test how much it helped.这很难测试它有多大帮助。 In some cases it helped tremendously, other times it didn't help at all, and in some cases it slowed it down by a small amount.在某些情况下,它有很大帮助,有时它根本没有帮助,在某些情况下,它会稍微减慢速度。 So I really don't know.所以我真的不知道。 I guess this is optional.我想这是可选的。
  • | Remove-Item -Recurse | Remove-Item -Recurse - Remove the directory, recursively (ensures directories that contain empty sub directories gets removed) | Remove-Item -Recurse - 递归删除目录(确保删除包含空子目录的目录)

If you're counting characters, this could be shortened to:如果您计算字符数,则可以缩短为:

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

If you really don't care about performance because you don't have that many files....even more so to:如果你真的不关心性能,因为你没有那么多文件......更是如此:

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

Side note: While playing around with this, I started testing various versions with Measure-Command against a server with millions of files and thousands of directories.旁注:在处理这个问题时,我开始使用Measure-Command对具有数百万个文件和数千个目录的服务器测试各种版本。

This is faster than the command I've been using (above):这比我一直使用的命令(上面)更快:

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

Recursively removing empty subdirectories can also be accomplished using a "For Loop".也可以使用“For 循环”来递归删除空子目录。

Before we start, let's make some subdirectories & text files to work with in $HOME\\Desktop\\Test在开始之前,让我们在 $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

First, store the following Script Block in the variable $SB.首先,将以下脚本块存储在变量 $SB 中。 The variable can be called later using the &SB command.稍后可以使用 &SB 命令调用该变量。 The &SB command will output a list of empty subdirectories contained in $HOME\\Desktop\\Test &SB 命令将输出包含在 $HOME\\Desktop\\Test 中的空子目录列表

$SB = {
    Get-ChildItem $HOME\Desktop\Test -Directory -Recurse |
    Where-Object {(Get-ChildItem $_.FullName -Force).Count -eq 0}
}

NOTE: The -Force parameter is very important.注意: -Force 参数非常重要。 It makes sure that directories which contain hidden files and subdirectories, but are otherwise empty, are not deleted in the "For Loop".它确保包含隐藏文件和子目录但为空的目录不会在“For 循环”中删除。

Now use a "For Loop" to recursively remove empty subdirectories in $HOME\\Desktop\\Test现在使用“For 循环”递归删除 $HOME\\Desktop\\Test 中的空子目录

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

Tested as working on PowerShell 4.0经测试可在 PowerShell 4.0 上运行

I have adapted the script of RichardHowells.我已经改编了理查德豪威尔斯的剧本。 It doesn't delete the folder if there is a thumbs.db.如果有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

I wouldn't take the comments/1st post to heart unless you also want to delete files that are nested more than one folder deep.除非您还想删除嵌套多个文件夹的文件,否则我不会将评论/第一篇文章放在心上。 You are going to end up deleting directories that may contain directories that may contain files.您最终将删除可能包含可能包含文件的目录的目录。 This is better:这个更好:

$FP= "C:\Temp\"

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

$Empty= $dirs | Where-Object {$_.GetFiles().Count -eq 0 **-and** $_.GetDirectories().Count -eq 0} | 

Select-Object FullName

The above checks to make sure the directory is in fact empty whereas the OP only checks to make sure there are no files.上述检查以确保目录实际上是空的,而 OP 仅检查以确保没有文件。 That in turn would result in files nexted a few folders deep also being deleted.这反过来又会导致紧邻几个文件夹深处的文件也被删除。

You may need to run the above a few times as it won't delete Dirs that have nested Dirs.您可能需要多次运行上述命令,因为它不会删除嵌套了 Dirs 的 Dirs。 So it only deletes the deepest level.所以它只删除最深的层次。 So loop it until they're all gone.所以循环它,直到它们都消失了。

Something else I do not do is use the -force parameter.我不做的其他事情是使用 -force 参数。 That is by design.那是设计使然。 If in fact remove-item hits a dir that is not empty you want to be prompted as an additional safety.如果实际上 remove-item 命中一个非空的目录,您希望被提示作为额外的安全。

Something like this works for me.像这样的东西对我有用。 The script delete empty folders and folders containing only folder (no files, no hidden files).该脚本删除空文件夹和仅包含文件夹(无文件,无隐藏文件)的文件夹。

$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
    }
}
$files = Get-ChildItem -Path c:\temp -Recurse -Force | where psiscontainer ; [array]::reverse($files)

[Array]::reverse($files) will reverse your items, so you get the lowest files in hierarchy first. [Array]::reverse($files)将反转您的项目,因此您首先获得层次结构中最低的文件。 I use this to manipulate filenames that have too long filepaths, before I delete them.在删除它们之前,我使用它来操作具有太长文件路径的文件名。

这是一个简单的方法

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

This will remove up all empty folders in the specified directory $tdc .这将删除指定目录$tdc中的所有空文件夹。 It is also a lot faster since there's no need for multiple runs.它也快得多,因为不需要多次运行。

    $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()
                }
            }
#By Mike Mike Costa Rica
$CarpetasVacias = Get-ChildItem -Path $CarpetaVer -Recurse -Force -Directory | Where {(gci $_.fullName).count -eq 0} | select Fullname,Name,LastWriteTime
$TotalCarpetas = $CarpetasVacias.Count
$CountSu = 1
ForEach ($UnaCarpeta in $CarpetasVacias){
    $RutaCarp = $UnaCarpeta.Fullname
    Remove-Item -Path $RutaCarp -Force -Confirm:$False -ErrorAction Ignore
    $testCar = Test-Path $RutaCarp
    if($testCar -eq $true){ 
        $Datem = (Get-Date).tostring("MM-dd-yyyy HH:mm:ss")
        Write-Host "$Datem ---> $CountSu de $TotalCarpetas Carpetas Error Borrando Directory: $RutaCarp" -foregroundcolor "red"
    }else{
        $Datem = (Get-Date).tostring("MM-dd-yyyy HH:mm:ss")
        Write-Host "$Datem ---> $CountSu de $TotalCarpetas Carpetas Correcto Borrando Directory: $RutaCarp" -foregroundcolor "gree"
    }
    $CountSu += 1
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM