Защищать цикл 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;
}

Однако, когда $ backups пусто, я получаю: 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, если список пуст?

STEB
источник
@Dan - Пробовал оба ($ backups> 0) и (@ ($ backups) .count -gt 0), но ни один из них не работает должным образом, когда нет файлов.
SteB

Ответы:

19

В 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
}
jscott
источник
Я использовал первый (это легче понять), я не мог заставить работать второй (я, вероятно, делал что-то не так).
SteB
@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, поэтому ошибка, описанная OP, больше не возникает. См. Раздел «Оператор ForEach не выполняет итерации по $ null» в публикации блога Powershell. Новые возможности языка V3 .
Джскотт
1

Я разработал решение, выполнив запрос дважды, один раз для получения файлов и один раз для подсчета файлов, приведя 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;
    }
}
STEB
источник
1
Я отредактировал пост для эффективности, так как это было проще, чем объяснять в комментариях. Просто отмените это, если вам это не нравится.
Дэн
@ Дэн - Дох, не могу поверить, что я этого не заметил, спасибо.
SteB
0

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

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

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

Дэн
источник
Добавление if ($ backups.count -gt 0) останавливает выполнение цикла, даже если в $ backups есть 1 элемент. $ backups.count даже сам по себе ничего не выводит.
SteB
@SteB Ах, я думаю, счетчик не реализован для любого типа объекта, содержащего данные. Я сканировал читать и предположил, что это был массив.
Дэн
$ count = @ ($ backups) .count; почти работает, но когда нет файлов, если f ($ count -gt 0) истинно!
SteB