Вопрос или проблема
Я пытаюсь рекурсивно удалить каталог с помощью rm -Force -Recurse somedirectory
, но получаю несколько ошибок “Каталог не пуст”. Если я повторяю ту же команду, она выполняется успешно.
Пример:
PS I:\Documents and Settings\m\My Documents\prg\net> rm -Force -Recurse .\FileHelpers
Remove-Item : Не удается удалить элемент I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data\RunTime\_svn: Каталог не пуст.
At line:1 char:3
+ rm <<<< -Force -Recurse .\FileHelpers
+ CategoryInfo : WriteError: (_svn:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Не удается удалить элемент I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data\RunTime: Каталог не пуст.
At line:1 char:3
+ rm <<<< -Force -Recurse .\FileHelpers
+ CategoryInfo : WriteError: (RunTime:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Не удается удалить элемент I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests\Data: Каталог не пуст.
At line:1 char:3
+ rm <<<< -Force -Recurse .\FileHelpers
+ CategoryInfo : WriteError: (Data:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Не удается удалить элемент I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\FileHelpers.Tests: Каталог не пуст.
At line:1 char:3
+ rm <<<< -Force -Recurse .\FileHelpers
+ CategoryInfo : WriteError: (FileHelpers.Tests:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Не удается удалить элемент I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs\nunit\_svn: Каталог не пуст.
At line:1 char:3
+ rm <<<< -Force -Recurse .\FileHelpers
+ CategoryInfo : WriteError: (_svn:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Не удается удалить элемент I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs\nunit: Каталог не пуст.
At line:1 char:3
+ rm <<<< -Force -Recurse .\FileHelpers
+ CategoryInfo : WriteError: (nunit:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Не удается удалить элемент I:\Documents and Settings\m\My Documents\prg\net\FileHelpers\Libs: Каталог не пуст.
At line:1 char:3
+ rm <<<< -Force -Recurse .\FileHelpers
+ CategoryInfo : WriteError: (Libs:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
Remove-Item : Не удается удалить элемент I:\Documents and Settings\m\My Documents\prg\net\FileHelpers: Каталог не пуст.
At line:1 char:3
+ rm <<<< -Force -Recurse .\FileHelpers
+ CategoryInfo : WriteError: (I:\Documents an...net\FileHelpers:DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
PS I:\Documents and Settings\m\My Documents\prg\net> rm -Force -Recurse .\FileHelpers
PS I:\Documents and Settings\m\My Documents\prg\net>
Конечно, это происходит не всегда. Также это происходит не только с каталогами _svn
, и у меня нет кэша TortoiseSVN или чего-то подобного, так что ничто не блокирует каталог.
Какие-нибудь идеи?
help Remove-Item
говорит:
Параметр Recurse в этом cmdlet работает неправильно.
и
Поскольку параметр Recurse в этом cmdlet неисправен, команда использует cmdlet Get-Childitem для получения желаемых файлов и использует оператор конвейера для передачи их в cmdlet Remove-Item.
и предлагает следующую альтернативу в качестве примера:
get-childitem * -include *.csv -recurse | remove-item
Таким образом, вы должны пропускать get-childitem -recurse
в remove-item
.
@JamesCW: Проблема все еще существует в PowerShell 4.0
Я попробовал другое решение, и оно сработало: используйте cmd.exe:
&cmd.exe /c rd /s /q $somedirectory
Обновление: Начиная с (по крайней мере) версии Windows 10 20H2 (я не знаю, Windows Server какой версии и сборки это соответствует; запустите winver.exe
, чтобы проверить вашу версию и сборку), функция Windows API DeleteFile
теперь демонстрирует синхронное поведение, которое неявно решает проблемы с Remove-Item
в PowerShell и .NET System.IO.File.Delete
/ System.IO.Directory.Delete
(но, любопытно, не с cmd.exe
rd /s
).
Существующие ответы уменьшают проблему, так что она происходит реже, но они не решают корневую причину, из-за которой сбои могут все еще происходить.
Remove-Item -Recurse
неожиданно асинхронен, в конечном итоге потому, что методы Windows API для удаления файлов и каталогов по своей сути асинхронны и Remove-Item
не учитывает это.
Это периодически и непредсказуемо проявляется одним из двух способов:
-
Ваш случай: Удаление самого непустого каталога может завершиться неудачей, если удаление подкаталога или файла в нем еще не завершено к тому времени, как будет предпринята попытка удалить родительский каталог.
-
Менее часто: Повторное создание удаленного каталога сразу после удаления может завершиться неудачей, поскольку удаление может еще не завершиться к тому времени, как будет предпринята попытка повторного создания.
Проблема затрагивает не только Remove-Item
в PowerShell, но также rd /s
в cmd.exe
, а также [System.IO.Directory]::Delete()
в .NET:
Начиная с Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.1 / cmd.exe
10.0.17134.407 / .NET Framework 4.7.03056, .NET Core 2.1, Remove-Item
, rd /s
, и [System.IO.Directory]::Delete()
не работают надежно, так как они не учитывают асинхронное поведение функций удаления файлов/каталогов в Windows API:
Для пользовательской функции PowerShell, которая обеспечивает надежное синхронное решение, см. этот ответ на SO.
ETA 20181217: PSVersion 4.0 и более поздние версии все еще будут выходить из строя в некоторых обстоятельствах, см. альтернативный ответ от Mehrdad Mirreza, и отчет о проблеме, поданный mklement
mklement предоставляет концептуальное решение в этом ответе на SO, пока ошибка ждет официального исправления
Новая версия PowerShell
(PSVersion 4.0
) полностью решила эту проблему, и Remove-Item "targetdirectory" -Recurse -Force
работает без проблем с синхронизацией.
Вы можете проверить вашу версию, запустив $PSVersiontable
в среде ISE или командной строке PowerShell
. Версия 4.0 поставляется с Windows 8.1
и Server 2012 R2
, и ее можно установить на более ранние версии Windows.
Ого. Много ответов. Я честно предпочитаю этот более всех остальных. Он супер простой, полный, читаемый и работает на любой Windows машине. Он использует (надежную) рекурсивную функцию удаления .NET, и если он вылетает по какой-то причине, он выдает верное исключение, которое может быть обработано с помощью блока try/catch.
$fullPath = (Resolve-Path "directory\to\remove").ProviderPath
[IO.Directory]::Delete($fullPath, $true)
Обратите внимание, что строка с Resolve-Path
важна, поскольку .NET не знает о вашем текущем каталоге при разрешении относительных путей к файлам. Вот и вся тонкость, о которой стоит помнить.
Текущий ответ не удалит каталог, а только его содержимое. Кроме того, у него будут проблемы с вложенными каталогами, так как он снова будет пытаться удалить каталог до его содержимого. Я написал нечто, что удаляет файлы в правильном порядке, но все равно иногда каталог все еще остается после этого.
Итак, теперь я использую кое-что, что поймает исключение, подождет и попытается снова (3 раза):
Пока я использую следующее:
function EmptyDirectory($directory = $(throw "Required parameter missing")) {
if ((test-path $directory) -and -not (gi $directory | ? { $_.PSIsContainer })) {
throw ("EmptyDirectory вызвана не для каталога.");
}
$finished = $false;
$attemptsLeft = 3;
do {
if (test-path $directory) {
rm $directory -recurse -force
}
try {
$null = mkdir $directory
$finished = $true
}
catch [System.IO.IOException] {
Start-Sleep -Milliseconds 500
}
$attemptsLeft = $attemptsLeft - 1;
}
while (-not $finished -and $attemptsLeft -gt 0)
if (-not $finished) {
throw ("Не удалось очистить и воссоздать каталог " + $directory)
}
}
Чтобы удалить каталог и его содержимое, требуется два шага.
Сначала удалите содержимое, затем саму папку. Используя обходной путь для неисправного рекурсивного элемента удаления, решение будет выглядеть так:
Get-ChildItem -Path "$folder\\*" -Recurse | Remove-Item -Force -Recurse
Remove-Item $folder
Таким образом, вы можете удалить и родительский каталог также.
Вот что у меня работает:
$Target = "c:\folder_to_delete"
Get-ChildItem -Path $Target -Recurse -force |
Where-Object { -not ($_.psiscontainer) } |
Remove-Item –Force
Remove-Item -Recurse -Force $Target
Первая линия удаляет все файлы в дереве. Вторая удаляет все папки, включая верхнюю.
Сначала возьмите на себя права на файлы/каталоги, используя Takeown.exe, затем удалите
https://learn-powershell.net/2014/06/24/changing-ownership-of-file-or-folder-using-powershell/
У меня была эта проблема с каталогом, который не удавалось удалить. Я обнаружил, что одна из подпапок была повреждена, и, когда я пытался переместить или переименовать эту дочернюю папку, я получал сообщение об ошибке, что она отсутствует. Я пытался использовать rm -Force и получал ту же ошибку, что и вы.
Что сработало для меня, так это сжатие родительского каталога с использованием 7-zip с опцией “Удалить файлы после сжатия”. После сжатия я смог удалить zip файл.
Решение выше работает для большинства моих скриптов. Но потом я нашел некоторые папки, на которых вышеуказанное решение не работало. Что исправило это для меня на стойкой папке, так это robocopy /PURGE
/PURGE: Удаляет файлы/папки в назначении, которые больше не существуют в источнике
Так что если вы создадите пустую папку, скопируйте ее в папку назначения с командой robocopy /PURGE. Robocopy очистит ее, затем вы можете удалить папку с помощью
$output_path | Remove-Item -Recurse -force
Мне удалось заставить это работать, перечислив все дочерние файлы и папки (с самыми дальними вначале) и удаляя каждый из них…
Это решение удаляет родительскую папку в обоих сценариях,
когда
-
дочерний файл может быть открыт или
-
дочерняя папка может быть открыта.
if ((Test-Path $targetFolderPath)) { # Очистка содержимого Get-ChildItem -Path $targetFolderPath -Recurse | Sort-Object { $_.FullName.Split('\').Count } -Descending | ForEach-Object { Remove-Item $_.FullName -Force } # Подождите немного, чтобы обеспечить удаление любых заблокированных файлов/каталогов Start-Sleep -Seconds 1 # Очистка родительской папки Remove-Item $targetFolderPath -Force }
Это мое решение для асинхронной ошибки “Каталог не пуст”, которую вызывает “Remove-Item” независимо от использования вами любых переключателей. У меня есть папки, названные для каждого дня недели, и я хочу очистить их перед началом следующей загрузки.
Если вы хотите оставить незатронутую всю структуру папок, опустите строки 2 – 4.
Если вы хотите оставить папки в корне, добавьте “*” к вашему пути в строке 2.
Get-ChildItem "$($backupPath)\$((Get-Date).DayOfWeek)" -Recurse | ? {!($_.PSisContainer)} | Remove-Item -Force -Recurse -Confirm:$false
foreach ($folder in ((Get-ChildItem "$($backupPath)\$((Get-Date).DayOfWeek)" -Recurse | Select FullName | Sort FullName -Descending).FullName)) {
Remove-Item -Path $folder -Force -Recurse -Confirm:$false
}
Ответ или решение
Удаление файлов и директорий в PowerShell может столкнуться с непредсказуемыми проблемами, особенно при использовании команды Remove-Item -Force -Recurse
. Столкнувшись с ошибками "The directory is not empty" и наблюдая, как повторное выполнение той же команды проходит успешно, возникает логичный вопрос о том, что действительно происходит в процессе работы с файловой системой.
Теория
Для понимания проблемы важно отметить, что асинхронность является одним из основных факторов, влияющих на поведение операций удаления в Windows. API операционной системы для работы с файлами и директориями, такие как функции DeleteFile
и RemoveDirectory
, реализованы с асинхронным поведением. Это означает, что после запроса на удаление файла или директории, фактическое завершение процесса может занять некоторое время, и попытка удаления родительской директории может закончиться неудачей, если при выполнении команды удаление вложенных файлов все еще не завершено.
Пример
Допустим, у нас в PowerShell есть необходимость удалять старые каталоги с отчётами, например, в директории "C:\Reports\2023". Применяя команду:
Remove-Item -Force -Recurse "C:\Reports\2023"
Пользователь может столкнуться с ошибками, указывающими на то, что директория не пуста, даже если все её поддиректории ранее успешно были удалены. Это связано с тем, что системные операции удаления снова и снова вызываются в асинхронном режиме, не дожидаясь завершения предыдущих запросов на удаление.
Применение
Справиться с данной проблемой можно несколькими способами, которые включают в себя использование PowerShell с учётом его особенностей, а также обходные решения.
-
Использование Get-ChildItem:
Один из популярных способов избежать проблемы "директория не пуста" — это сначала удалить все вложенные файлы и директории с помощью
Get-ChildItem
и только затем удалять саму директорию:Get-ChildItem -Path "C:\Reports\2023" -Recurse | Remove-Item -Force Remove-Item -Path "C:\Reports\2023" -Force
Таким образом, сначала удалятся все файлы и вложенные директории, и только потом родительская.
-
Синхронизация удаления:
Недавно были введены улучшения в API функций Windows, что позволяет сделать операции синхронными. Если используется актуальная версия Windows 10 или новее, то в ней проблема решена на системном уровне. Необходимо убедиться, что используется актуальная версия через выполнение
winver.exe
. -
Использование .NET объектов:
Если PowerShell всё равно генерирует ошибки, можно воспользоваться методами .NET, которые порой более надёжны в плане управления ресурсами:
$directoryPath = (Resolve-Path "C:\Reports\2023").ProviderPath [System.IO.Directory]::Delete($directoryPath, $true)
Однако важно помнить об абсолютных путях —
Resolve-Path
преобразует путь в абсолютный, что нейтрализует одну из распространенных ошибок при использовании относительных путей. -
Ретраймирование и исключения:
Чтобы гарантированно удалить все объекты, также можно ввести алгоритм с ретраймированием, что позволит повторить попытку удаления после небольшой паузы, если это потребуется:
function SafeRemove-Directory($directory) { $retries = 3 for ($i = 0; $i -lt $retries; $i++) { try { Remove-Item -Path $directory -Recurse -Force if (-not (Test-Path $directory)) { return } } catch { Start-Sleep -Milliseconds 500 } } throw "Не удалось удалить директорию $directory после $retries попыток." }
Эти методы и подходы помогут минимизировать число ошибок при удалении директорий и обеспечить более надёжное управление файловой системой через PowerShell. Важно постоянно обновлять как саму оболочку PowerShell, так и систему, чтобы иметь на борту все исправления и улучшения, выпущенные разработчиками.