[英]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:在查看这样的问题时,您需要牢记一些关键事项:
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.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-Item
和Get-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.然后,您可以遍历它以删除项目。
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
- 在找到的第一个文件处停止
| 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.