Захистіть цикл foreach, коли порожній список


10

За допомогою 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;
}

Однак коли $ резервні копії порожні, я отримую: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

Я спробував:

  1. Захист foreach с if (!$backups)
  2. Захист видалення елемента за допомогою if (Test-Path $file -PathType Leaf)
  3. Захист видалення елемента за допомогою if ([IO.File]::Exists($file.FullName) -ne $true)

Жоден із них не працює, що робити, якщо рекомендований спосіб запобігти введенню циклу foreach, якщо список порожній?


@Dan - Спробував і ($ резервні копії> 0), і (@ ($ резервні копії) .count -gt 0), але не працює, як очікувалося, коли файлів немає.
Стеб

Відповіді:


19

З Powershell 3 foreachзаява не повторюється, $nullі проблема, описана ОП, більше не виникає.

З публікації щоденника 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командлет, загорнутий у масив. Це також буде працювати з функціями, які можуть повернути a $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
}

Я використав перший (це простіше зрозуміти), не міг змусити другий працювати (я, мабуть, робив щось не так).
Стеб

@SteB Ви маєте рацію, мій приклад був погано пояснений (він все ще є), але я надав редагування, включаючи ваш код прикладу. Для кращого пояснення поведінки, будь ласка, дивіться цю публікацію в блозі Кіта Хілла , він не тільки експерт PowerShell, але і набагато кращий письменник, ніж я. Кіт активний у StackOverflow , я б закликав вас (або когось зацікавив PS) перевірити його речі.
jscott

2

Я знаю, що це стара публікація, але я хотів би зазначити, що командлет 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-об’єкт може бути трохи волохатим, особливо якщо ви вкладаєте гніздо, оскільки вам важко слідувати посиланням $ _. У будь-якому випадку для такої невеликої операції це ідеально. Багато людей також стверджують, що це швидше, але я виявив, що це лише трохи.


+1 Але з Powershell 3 (близько червня 2012 р.) foreachЗаява більше не вступає $null, тому помилка, описана ОП, більше не виникає. Дивіться розділ "Заява ForEach не повторює більше $ null" у блозі Powershell Нові мовні функції V3 .
jscott

1

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

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

1
Я відредагував пост для ефективності, оскільки це було простіше, ніж пояснення в коментарях. Просто поверніть його, якщо вам це не подобається.
День

@Dan - До, не можу повірити, я цього не помітив, дякую.
Стеб

0

Використовуйте наступне, щоб оцінити, чи має масив якийсь вміст:

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

Якщо змінної не існує, Powershell просто оцінить її як помилкову, тому не потрібно перевіряти, чи існує вона.


Додавання, якщо ($ backups.count -gt 0) зупиняє цикл виконання, навіть якщо в $ резервних копіях є 1 елемент. $ backups.count навіть самостійно нічого не дає.
Стеб

@SteB Ага, я думаю, підрахунок не реалізований для того, який тип об'єкта містить дані. Я сканував прочитане і вважав, що це масив.
Дан

$ count = @ ($ резервного копіювання) .count; майже працює, але коли файлів немає, якщо f ($ count -gt 0) вірно!
Стеб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.