簡體   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