Назад | Перейти на главную страницу

Защитить цикл foreach при пустом списке

Используя Powershell v2.0, я хочу удалить любые файлы старше X дней:

$backups = Get-ChildItem -Path $Backuppath | 
                Where-Object {($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and (-not $_.PSIsContainer) -and ($_.Name -like "backup*")}

foreach ($file in $backups)
{
    Remove-Item $file.FullName;
}

Однако, когда $ backups пуст, я получаю: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

Я пробовал:

  1. Защита foreach с помощью if (!$backups)
  2. Защита Remove-Item с помощью if (Test-Path $file -PathType Leaf)
  3. Защита Remove-Item с помощью if ([IO.File]::Exists($file.FullName) -ne $true)

Кажется, что ни один из них не работает, что делать, если рекомендуемый способ предотвращения входа в цикл foreach, если список пуст?

С Powershell 3 foreach оператор не повторяется $null и проблема, описанная OP, больше не возникает.

Из Блог Windows PowerShell Почта Новые возможности языка V3:

Оператор ForEach не выполняет итерацию по $ null

В PowerShell V2.0 людей часто удивляли:

PS> foreach ($i in $null) { 'got here' }

got here

Такая ситуация часто возникает, когда командлет не возвращает никаких объектов. В PowerShell V3.0 вам не нужно добавлять оператор if, чтобы избежать итерации по $ null. Мы позаботимся об этом за вас.

Для PowerShell $PSVersionTable.PSVersion.Major -le 2 см. следующий исходный ответ.


У вас есть два варианта, я в основном использую второй.

Проверьте $backups для не $null. Простой If вокруг цикла можно проверить, нет ли $null

if ( $backups -ne $null ) {

    foreach ($file in $backups) {
        Remove-Item $file.FullName;
    }

}

Или

Инициализировать $backups как нулевой массив. Это позволяет избежать двусмысленности проблемы "повторения пустого массива". вы спрашивали в своем последнем вопросе.

$backups = @()
# $backups is now a null value array

foreach ( $file in $backups ) {
    # this is not reached.
    Remove-Item $file.FullName
}

Извините, я не предоставил пример интеграции вашего кода. Обратите внимание Get-ChildItem командлет, завернутый в массив. Это также будет работать с функциями, которые могут возвращать $null.

$backups = @(
    Get-ChildItem -Path $Backuppath |
        Where-Object { ($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and (-not $_.PSIsContainer) -and ($_.Name -like "backup*") }
)

foreach ($file in $backups) {
    Remove-Item $file.FullName
}

Я знаю, что это старый пост, но я хотел бы отметить, что командлет ForEach-Object не страдает той же проблемой, что и использование ключевого слова ForEach. Таким образом, вы можете передать результаты DIR в ForEach и просто сослаться на файл с помощью $ _, например:

$backups | ForEach{ Remove-Item $_ }

Фактически вы можете перенаправить саму команду Dir через конвейер и даже не назначать переменную, например:

Get-ChildItem -Path $Backuppath | 
Where-Object {
             ($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and `
             (-not $_.PSIsContainer) -and ($_.Name -like "backup*")
             } |
ForEach{ Remove-Item $_ }

Я добавил разрывы строк для удобства чтения.

Я понимаю некоторых людей, которым нравится ForEach / In для удобства чтения. Иногда ForEach-object может показаться непростым, особенно если вы выполняете вложение, так как становится трудно следовать ссылке $ _. Во всяком случае, для такой небольшой операции он идеален. Многие люди также утверждают, что это быстрее, но я обнаружил, что это не так.

Я разработал решение, выполнив запрос дважды, один раз для получения файлов и один раз для подсчета файлов, приведя get-ChilItem для возврата массива (преобразование $ backups в качестве массива после факта, похоже, не работает) .
По крайней мере, он работает так, как ожидалось (производительность не должна быть такой проблемой, поскольку никогда не будет больше дюжины файлов), если кто-то знает решение для одного запроса, опубликуйте его.

$count = @(Get-ChildItem -Path $zipFilepath | 
                Where-Object {($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and (-not $_.PSIsContainer) -and ($_.Name -like $partial + "*")}).count;

if ($count -gt 0)
{
    $backups = Get-ChildItem -Path $zipFilepath | 
                Where-Object {($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and (-not $_.PSIsContainer) -and ($_.Name -like $partial + "*")};

    foreach ($file in $backups)
    {
        Remove-Item $file.FullName;
    }
}

Используйте следующее, чтобы оценить, есть ли в массиве какое-либо содержимое:

if($backups.count -gt 0) { echo "Array has contents" } else { echo "Array is empty" }

Если переменная не существует, Powershell просто оценит ее как ложную, поэтому нет необходимости проверять, существует ли она.