繁体   English   中英

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

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

我需要递归删除 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

我在 Windows 8.1 版本上使用 PowerShell。

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

  1. Get-ChildItem -Recurse执行头递归,这意味着它在遍历树时一旦找到文件夹就返回文件夹。 由于您要删除空文件夹,并且在删除空文件夹后如果它们为空,还要删除其父文件夹,因此您需要改用尾递归,它处理从最深子文件夹到根文件夹的文件夹。 通过使用尾递归,无需重复调用删除空文件夹的代码——一次调用即可为您完成所有工作。
  2. 默认情况下, Get-ChildItem不返回隐藏文件或文件夹。 因此,您需要采取额外的步骤来确保您不会删除看起来为空但包含隐藏文件或文件夹的文件夹。 Get-ItemGet-ChildItem都有一个-Force参数,可用于检索隐藏文件或文件夹以及可见文件或文件夹。

考虑到这些要点,这里是一种使用尾递归并正确跟踪隐藏文件或文件夹的解决方案,确保删除为空的隐藏文件夹,并确保保留可能包含一个或多个隐藏文件的文件夹。

首先这是完成工作的脚本块(匿名函数):

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

如果你想测试它,这里的代码将创建有趣的测试数据(确保你还没有文件夹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

这是你如何使用它。 请注意,如果该文件夹在删除其下的所有空文件夹后最终为空,这将删除顶部文件夹(本示例中的C:\\a文件夹,如果您使用上面的脚本生成测试数据,则会创建该文件夹)。

& $tailRecursion -Path 'C:\a'

你可以使用这个:

$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)
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 进行反向排序。 这将确保我们始终在处理父项之前处理子项(即 Kirk Munro 的回答所描述的“尾递归”)。 这使得它递归地删除空文件夹。

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

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

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

对于$tdc的情况,它将是

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

如果您只是想确保只删除可能包含子文件夹但不删除其本身及其子文件夹中的文件的文件夹,这可能是一种更简单、更快捷的方法。

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

只是想我会在这里为已经很长的答案列表做出贡献。

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

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

它不考虑隐藏文件/文件夹,但您可以通过将-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 | Select-Object -First 1 - 在找到的第一个文件处停止
    • 这很难测试它有多大帮助。 在某些情况下,它有很大帮助,有时它根本没有帮助,在某些情况下,它会稍微减慢速度。 所以我真的不知道。 我想这是可选的。
  • | Remove-Item -Recurse | 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

也可以使用“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 上运行

我已经改编了理查德豪威尔斯的剧本。 如果有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

除非您还想删除嵌套多个文件夹的文件,否则我不会将评论/第一篇文章放在心上。 您最终将删除可能包含可能包含文件的目录的目录。 这个更好:

$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

上述检查以确保目录实际上是空的,而 OP 仅检查以确保没有文件。 这反过来又会导致紧邻几个文件夹深处的文件也被删除。

您可能需要多次运行上述命令,因为它不会删除嵌套了 Dirs 的 Dirs。 所以它只删除最深的层次。 所以循环它,直到它们都消失了。

我不做的其他事情是使用 -force 参数。 那是设计使然。 如果实际上 remove-item 命中一个非空的目录,您希望被提示作为额外的安全。

像这样的东西对我有用。 该脚本删除空文件夹和仅包含文件夹(无文件,无隐藏文件)的文件夹。

$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)将反转您的项目,因此您首先获得层次结构中最低的文件。 在删除它们之前,我使用它来操作具有太长文件路径的文件名。

这是一个简单的方法

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

这将删除指定目录$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()
                }
            }
#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