Подтвердить что ты не робот

Как рекурсивно удалить все пустые папки в 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

Я использую PowerShell в версии Windows 8.1.

4b9b3361

Ответ 1

Вы можете использовать это:

$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 после фильтрации. Затем вы можете перебрать его, чтобы удалить элементы.

Update

Если вы хотите удалить каталоги, содержащие пустые каталоги, вам просто нужно продолжать работать script, пока они не исчезнут. Вы можете зацикливаться до тех пор, пока $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)

Ответ 2

При рассмотрении такой проблемы необходимо помнить несколько ключевых моментов:

  1. Get-ChildItem -Recurse выполняет рекурсию головы, то есть возвращает папки, как только находит их, проходя по дереву. Поскольку вы хотите удалить пустые папки, а также удалить их родительские папки, если они будут пустыми после удаления пустых папок, вам нужно вместо этого использовать хвостовую рекурсию, которая обрабатывает папки от самого глубокого дочернего элемента до корня. При использовании хвостовой рекурсии не будет необходимости повторных вызовов кода, который удаляет пустые папки - один вызов сделает все за вас.
  2. Get-ChildItem не возвращает скрытые файлы или папки по умолчанию. В результате вам нужно предпринять дополнительные шаги, чтобы убедиться, что вы не удаляете папки, которые кажутся пустыми, но содержат скрытые файлы или папки. Get-Item и Get-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'

Ответ 3

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

Ответ 4

Я бы не принял комментарии /1-е сообщение близко к сердцу, если вы не хотите удалять файлы, вложенные более чем в одну папку. Вы собираетесь в конечном итоге удалить каталоги, которые могут содержать каталоги, которые могут содержать файлы. Это лучше:

$ FP = "C:\Temp \"

$ dirs = Get-Childitem -LiteralPath $ FP -directory -ресурс

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

Select-Object FullName

Вышеприведенное проверяет, чтобы убедиться, что каталог на самом деле пуст, тогда как OP только проверяет, нет ли файлов. Это, в свою очередь, приведет к удалению файлов, расположенных в нескольких папках.

Возможно, вам придется запустить вышеупомянутое несколько раз, так как он не удалит каталоги с вложенными каталогами. Таким образом, он удаляет только самый глубокий уровень. Так что зацикливайся, пока они все не исчезнут.

Еще я не использую параметр -force. Это по замыслу. Если на самом деле remove-item попадает в директорию, которая не пуста, вы хотите получить дополнительную информацию.

Ответ 5

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. Это гарантирует, что мы всегда обрабатываем детей, прежде чем мы обрабатываем родителей. Это позволяет рекурсивно удалять пустые папки.

Я не уверен, улучшит ли Select-Object -First 1 производительность или нет, но это возможно.

Ответ 6

Рекурсивное удаление пустых подкаталогов также может быть выполнено с помощью "For Loop" .

Прежде чем мы начнем, позвольте сделать некоторые подкаталоги и текстовые файлы для работы в $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

Сначала сохраните следующий Script Блок в переменной $SB. Эта переменная может быть вызвана позже, используя команду "amp; SB". Команда & SB выведет список пустых подкаталогов, содержащийся в $HOME\Desktop\Test

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

ПРИМЕЧАНИЕ. Параметр -Force очень важен. Он гарантирует, что каталоги, содержащие скрытые файлы и подкаталоги, но в противном случае пустые, не удаляются в "For Loop" .

Теперь используйте "For Loop" для рекурсивного удаления пустых подкаталогов в $HOME\Desktop\Test

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

Протестировано как работающее на PowerShell 4.0

Ответ 7

Предполагая, что вы находитесь в интересующей родительской папке

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

Для вашего случая с $tdc это будет

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

Ответ 8

Если вы просто хотите убедиться, что вы удаляете только папки, которые могут содержать подпапки, но не содержат файлов внутри себя и в подпапках, это может быть проще и быстрее.

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

Ответ 9

Я адаптировал сценарий Ричарда Хоуэллса. Папка не удаляется, если есть 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

Ответ 10

Как-то так у меня работает. Сценарий удаляет пустые папки и папки, содержащие только папки (без файлов, без скрытых файлов).

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