Chroń pętlę foreach, gdy pusta lista

10

Za pomocą programu Powershell v2.0 Chcę usunąć wszystkie pliki starsze niż X dni:

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

Jednak gdy kopie zapasowe $ są puste, otrzymuję: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

Próbowałem:

  1. Ochrona foreach za pomocą if (!$backups)
  2. Ochrona elementu usuwanego za pomocą if (Test-Path $file -PathType Leaf)
  3. Ochrona elementu usuwanego za pomocą if ([IO.File]::Exists($file.FullName) -ne $true)

Wydaje się, że żaden z nich nie działa, a jeśli zalecany sposób zapobiegania wprowadzaniu pętli foreach, jeśli lista jest pusta?

SteB
źródło
@Dan - Próbowałem zarówno ($ backups> 0), jak i (@ ($ backups) .count -gt 0), ale żadne nie działa zgodnie z oczekiwaniami, gdy nie ma plików.
SteB,

Odpowiedzi:

19

W Powershell 3 foreachinstrukcja nie jest powtarzana, $nulla problem opisany przez OP już nie występuje.

Z postu na blogu Windows PowerShell Nowe funkcje języka V3 :

Instrukcja ForEach nie wykonuje iteracji po wartości $ null

W programie PowerShell V2.0 ludzie często byli zaskoczeni:

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

got here

Taka sytuacja często pojawia się, gdy polecenie cmdlet nie zwraca żadnych obiektów. W PowerShell V3.0 nie trzeba dodawać instrukcji if, aby uniknąć iteracji powyżej $ null. Dbamy o to za Ciebie.

W przypadku programu PowerShell $PSVersionTable.PSVersion.Major -le 2zobacz oryginalną odpowiedź poniżej.


Masz dwie opcje, najczęściej używam drugiej.

Sprawdź, $backupsczy nie $null. Prosta Ifpętla może sprawdzić, czy nie$null

if ( $backups -ne $null ) {

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

}

Lub

Zainicjuj $backupsjako tablica zerowa. Pozwala to uniknąć dwuznaczności problemu „iteruj pustą tablicę” , o który pytałeś w swoim ostatnim pytaniu .

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

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

Przepraszam, ale nie podałem przykładu integrującego twój kod. Zwróć uwagę na Get-ChildItemcmdlet zawinięty w tablicę. Działa to również z funkcjami, które mogą zwrócić 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
}
jscott
źródło
Użyłem pierwszego (łatwiej to zrozumieć), nie mogłem zmusić drugiego do pracy (prawdopodobnie robiłem coś złego).
SteB
@SteB Masz rację, mój przykład został źle wyjaśniony (nadal jest), ale dostarczyłem edycję zawierającą przykładowy kod. Aby uzyskać lepsze wyjaśnienie tego zachowania, zobacz ten post na blogu Keitha Hill'a , on jest nie tylko ekspertem PowerShell, ale także znacznie lepszym pisarzem niż ja. Keith jest aktywny na StackOverflow , zachęcam cię (lub każdego zainteresowanego PS) do sprawdzenia jego rzeczy.
jscott,
2

Wiem, że to stary post, ale chciałbym zauważyć, że polecenie cmdlet ForEach-Object nie ma tego samego problemu, co użycie słowa kluczowego ForEach. Możesz więc przesłać wyniki DIR do ForEach i po prostu odwołać się do pliku za pomocą $ _, na przykład:

$backups | ForEach{ Remove-Item $_ }

Możesz faktycznie przesłać polecenie Dir przez potok i unikać nawet przypisywania zmiennej, takiej jak:

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

Dodałem podziały linii dla czytelności.

Rozumiem niektóre osoby, takie jak ForEach / In, pod względem czytelności. Czasami obiekt ForEach może być trochę owłosiony, szczególnie jeśli zagnieżdżasz się, ponieważ trudno jest podążać za referencją $ _. W każdym razie do takiej małej operacji jest idealna. Wiele osób twierdzi również, że jest to szybsze, ale stwierdziłem, że jest to tylko nieznacznie.

Steven
źródło
+1 Ale w Powershell 3 (około czerwca 2012 r.) foreachInstrukcja już nie wkracza $null, więc błąd opisany przez OP już nie występuje. Zobacz sekcję „Instrukcja ForEach nie przechodzi przez $ null” w blogu Powershell Nowe funkcje języka V3 .
jscott
1

Opracowałem rozwiązanie, uruchamiając zapytanie dwukrotnie, raz, aby pobrać pliki i raz policzyć pliki, przesyłając get-ChilItem w celu zwrócenia tablicy (rzutowanie $ backups jako tablicy po tym, jak wydaje się to nie działać) .
Przynajmniej działa zgodnie z oczekiwaniami (wydajność nie powinna być tak istotna, ponieważ nigdy nie będzie więcej niż tuzin plików), jeśli ktoś wie o rozwiązaniu do jednego zapytania, opublikuj je.

$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
źródło
1
Zredagowałem post pod kątem wydajności, ponieważ było to łatwiejsze niż wyjaśnienie w komentarzach. Po prostu cofnij, jeśli ci się nie podoba.
Dan
@Dan - Doh, nie mogę uwierzyć, że tego nie zauważyłem, dzięki.
SteB,
0

Wykonaj następujące czynności, aby ocenić, czy tablica ma jakąkolwiek zawartość:

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

Jeśli zmienna nie istnieje, PowerShell po prostu oceni ją jako fałsz, więc nie trzeba sprawdzać, czy istnieje.

Dan
źródło
Dodanie if ($ backups.count -gt 0) zatrzymuje wykonywanie pętli, nawet jeśli w $ backupach jest 1 element. $ backups.count nawet na własną rękę niczego nie wyświetla.
SteB,
@SteB Ach, myślę, że liczenie nie jest zaimplementowane dla dowolnego typu obiektu przechowującego dane. Przeglądam czytać i zakładam, że to tablica.
Dan
$ count = @ ($ backups) .count; prawie działa, ale gdy nie ma plików, jeśli f ($ count -gt 0) jest prawdziwe!
SteB,