Hoe recursief alle lege mappen in PowerShell verwijderen?

Ik moet recursief alle lege mappen voor een specifieke map in PowerShell verwijderen (controleer de map en submap op elk niveau).

Op dit moment gebruik ik dit script zonder succes.

Kunt u me alstublieft vertellen hoe ik dit kan oplossen?

$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

Ik gebruik PowerShell op Windows 8.1-versie.


Antwoord 1, autoriteit 100%

Je moet een paar belangrijke dingen in gedachten houden als je naar een probleem als dit kijkt:

  1. Get-ChildItem -Recursevoert head-recursie uit, wat inhoudt dat het mappen retourneert zodra het ze vindt wanneer het door een boom loopt. Omdat je lege mappen wilt verwijderen en ook hun bovenliggende map wilt verwijderen als ze leeg zijn nadat je de lege mappen hebt verwijderd, moet je in plaats daarvan staartrecursie gebruiken, die de mappen van het diepste kind tot aan de root verwerkt. Door staartrecursie te gebruiken, is het niet nodig om de code die de lege mappen verwijdert herhaaldelijk aan te roepen – één aanroep doet het allemaal voor u.
  2. Get-ChildItemretourneert standaard geen verborgen bestanden of mappen. Als gevolg hiervan moet u extra stappen ondernemen om ervoor te zorgen dat u geen mappen verwijdert die leeg lijken maar verborgen bestanden of mappen bevatten. Get-Itemen Get-ChildItemhebben beide een -Forceparameter die kan worden gebruikt om verborgen bestanden of mappen op te halen, evenals zichtbare bestanden of mappen.

Met deze punten in gedachten is hier een oplossing die gebruik maakt van staartrecursie en die verborgen bestanden of mappen op de juiste manier bijhoudt, waarbij je ervoor zorgt dat verborgen mappen worden verwijderd als ze leeg zijn en dat je ook mappen bewaart die een of meer verborgen kunnen bevatten bestanden.

Eerst is dit het scriptblok (anonieme functie) dat het werk doet:

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

Als je het wilt testen, is hier code die interessante testgegevens zal creëren (zorg ervoor dat je niet al een map c:\ahebt omdat deze zal worden verwijderd):

# 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

Zo gebruik je het. Houd er rekening mee dat hierdoor de bovenste map wordt verwijderd (de map C:\ain dit voorbeeld, die wordt gemaakt als u de testgegevens hebt gegenereerd met behulp van het bovenstaande script) als die map leeg blijkt te zijn na het verwijderen van alle lege mappen eronder.

& $tailRecursion -Path 'C:\a'

Antwoord 2, autoriteit 88%

U kunt dit gebruiken:

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

$dirsis een array van lege mappen die na het filteren wordt geretourneerd door de opdracht Get-ChildItem. Je kunt er dan overheen lopen om de items te verwijderen.

Bijwerken

Als u mappen met lege mappen wilt verwijderen, moet u het script gewoon blijven uitvoeren totdat ze allemaal verdwenen zijn. U kunt herhalen totdat $dirsleeg is:

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

Als u er zeker van wilt zijn dat verborgen bestanden en mappen ook worden verwijderd, voegt u de vlag -Forcetoe:

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)

Antwoord 3, autoriteit 39%

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

De enige nieuwe bijdrage hier is het gebruik van Sort-Objectom omgekeerd te sorteren op de volledige naam van de directory. Dit zorgt ervoor dat we altijd kinderen verwerken voordat we ouders verwerken (d.w.z. “staartrecursie” zoals beschreven in het antwoord van Kirk Munro). Dat maakt het recursief lege mappen te verwijderen.

Uit de hand weet ik niet zeker of de Select-Object -First 1de prestaties aanzienlijk zal verbeteren of niet, maar het zou kunnen.


Antwoord 4, autoriteit 9%

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

Antwoord 5, autoriteit 6%

Ik dacht dat ik zou bijdragen aan de toch al lange lijst met antwoorden hier.

Veel van de antwoorden hebben eigenaardigheden, zoals de noodzaak om meer dan één keer te rennen. Andere zijn te ingewikkeld voor de gemiddelde gebruiker (zoals het gebruik van staartrecursie om dubbele scans te voorkomen, enz.).

Hier is een heel eenvoudige oneliner die ik al jaren gebruik en die prima werkt…

Het houdt geen rekening met verborgen bestanden/mappen, maar u kunt dit oplossen door -Forcetoe te voegen aan de opdracht Get-ChildItem

Dit is de lange, volledig gekwalificeerde versie van de cmdletnaam:

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

Dus eigenlijk… zo gaat het:

  • Get-ChildItem -Recurse -Directory– Begin recursief te scannen op zoek naar mappen
  • $_.EnumerateFiles('*',1)– Voor elke map…Stel de bestanden op
    • EnumerateFileszal zijn bevindingen uitvoeren zoals het gaat, GetFileszal uitvoeren wanneer het klaar is….althans, zo zou het moeten werken in .NET …om de een of andere reden in PowerShell GetFilesonmiddellijk begint te spugen. Maar ik gebruik nog steeds EnumerateFilesomdat het tijdens het testen betrouwbaar sneller was.
    • ('*',1)betekent ALLE bestanden recursief zoeken.
  • | Select-Object -First 1– Stop bij het eerste gevonden bestand
    • Dit was moeilijk te testen hoeveel het hielp. In sommige gevallen hielp het enorm, andere keren hielp het helemaal niet, en in sommige gevallen vertraagde het het een klein beetje. Dus ik weet het echt niet. Ik denk dat dit optioneel is.
  • | Remove-Item -Recurse– Verwijder de map, recursief (zorgt ervoor dat mappen met lege submappen worden verwijderd)

Als je tekens telt, kan dit worden ingekort tot:

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

Als je echt niet om prestaties geeft omdat je niet zoveel bestanden hebt….nog meer om:

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

Kanttekening:
Terwijl ik hiermee aan het spelen was, begon ik verschillende versies met Measure-Commandte testen op een server met miljoenen bestanden en duizenden mappen.

Dit is sneller dan het commando dat ik heb gebruikt (hierboven):

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

Antwoord 6

Ervan uitgaande dat u zich in de betreffende bovenliggende map bevindt

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

Voor jouw geval met $tdcis het

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


Antwoord 7

Als u er zeker van wilt zijn dat u alleen mappen verwijdert die mogelijk submappen bevatten, maar geen bestanden in zichzelf en de bijbehorende submappen, kan dit een eenvoudigere en snellere manier zijn.

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

Antwoord 8

Het recursief verwijderen van lege submappen kan ook met een “For Loop”.

Laten we, voordat we beginnen, enkele submappen maken & tekstbestanden om mee te werken in $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

Sla eerst het volgende Script Block op in de variabele $SB. De variabele kan later worden aangeroepen met het &SB-commando. Het &SB-commando geeft een lijst met lege subdirectories uit $HOME\Desktop\Test

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

OPMERKING: De parameter -Force is erg belangrijk. Het zorgt ervoor dat mappen die verborgen bestanden en submappen bevatten, maar verder leeg zijn, niet worden verwijderd in de “For Loop”.

Gebruik nu een “For Loop” om recursief lege submappen te verwijderen in $HOME\Desktop\Test

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

Getest als werkend op PowerShell 4.0


Antwoord 9

Dit is een eenvoudige benadering

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

Antwoord 10

Hiermee worden alle lege mappen in de opgegeven map $tdcverwijderd.
Het is ook een stuk sneller omdat er niet meerdere runs nodig zijn.

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

Antwoord 11

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

Other episodes