Schützen Sie jede Schleife, wenn die Liste leer ist

10

Mit Powershell v2.0 möchte ich alle Dateien löschen, die älter als X Tage sind:

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

Wenn jedoch $ backups leer ist, erhalte ich: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

Ich habe es versucht:

  1. Schutz des Foreach mit if (!$backups)
  2. Schutz des Entfernungsgegenstandes mit if (Test-Path $file -PathType Leaf)
  3. Schutz des Entfernungsgegenstandes mit if ([IO.File]::Exists($file.FullName) -ne $true)

Keines davon scheint zu funktionieren. Was ist, wenn die empfohlene Methode, um zu verhindern, dass eine foreach-Schleife eingegeben wird, wenn die Liste leer ist?

SteB
quelle
@Dan - Versuchte sowohl ($ backups> 0) als auch (@ ($ backups) .count -gt 0), aber beide funktionieren nicht wie erwartet, wenn keine Dateien vorhanden sind.
SteB

Antworten:

19

Mit Powershell 3 wird die foreachAnweisung nicht wiederholt $nullund das von OP beschriebene Problem tritt nicht mehr auf.

Von der Windows Powershell - Blog Post New V3 Sprach - Features :

Die ForEach-Anweisung iteriert nicht über $ null

In PowerShell V2.0 waren die Benutzer häufig überrascht von:

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

got here

Diese Situation tritt häufig auf, wenn ein Cmdlet keine Objekte zurückgibt. In PowerShell V3.0 müssen Sie keine if-Anweisung hinzufügen, um zu vermeiden, dass über $ null iteriert wird. Wir kümmern uns darum für Sie.

Für PowerShell finden $PSVersionTable.PSVersion.Major -le 2Sie die ursprüngliche Antwort im Folgenden.


Sie haben zwei Möglichkeiten, ich benutze meistens die zweite.

Überprüfen Sie, ob dies $backupsnicht der Fall ist $null. Eine einfache Ifum die Schleife kann auf nicht prüfen$null

if ( $backups -ne $null ) {

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

}

Oder

$backupsAls Null-Array initialisieren . Dies vermeidet die Mehrdeutigkeit des Problems "iteriertes leeres Array", nach dem Sie in Ihrer letzten Frage gefragt haben .

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

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

Entschuldigung, ich habe es versäumt, ein Beispiel für die Integration Ihres Codes anzugeben. Beachten Get-ChildItemSie das im Array eingeschlossene Cmdlet. Dies würde auch mit Funktionen funktionieren, die a zurückgeben könnten $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
quelle
Ich habe den ersten verwendet (es ist leichter zu verstehen), ich konnte den zweiten nicht zum Laufen bringen (ich habe wahrscheinlich etwas falsch gemacht).
SteB
@SteB Sie haben Recht, mein Beispiel wurde schlecht erklärt (es ist immer noch so), aber ich habe eine Bearbeitung einschließlich Ihres Beispielcodes bereitgestellt. Eine bessere Erklärung des Verhaltens finden Sie in diesem Beitrag auf Keith Hills Blog . Er ist nicht nur ein PowerShell-Experte, sondern auch ein weitaus besserer Autor als ich. Keith ist auf StackOverflow aktiv . Ich möchte Sie (oder jeden, der sich für PS interessiert) ermutigen, sich seine Sachen anzusehen.
Jscott
2

Ich weiß, dass dies ein alter Beitrag ist, aber ich möchte darauf hinweisen, dass das Cmdlet ForEach-Object nicht das gleiche Problem hat wie die Verwendung des Schlüsselworts ForEach. Sie können also die Ergebnisse von DIR an ForEach weiterleiten und einfach mit $ _ auf die Datei verweisen, z.

$backups | ForEach{ Remove-Item $_ }

Sie können den Dir-Befehl selbst über die Pipe weiterleiten und vermeiden, die Variable wie folgt zuzuweisen:

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

Ich habe Zeilenumbrüche zur besseren Lesbarkeit hinzugefügt.

Ich verstehe einige Leute wie ForEach / In für die Lesbarkeit. Manchmal kann das ForEach-Objekt etwas haarig werden, besonders wenn Sie verschachteln, da es schwierig wird, der $ _ - Referenz zu folgen. Auf jeden Fall ist es für eine kleine Operation wie diese perfekt. Viele Leute behaupten auch, es sei schneller, aber ich habe festgestellt, dass dies nur geringfügig ist.

Steven
quelle
+1 Bei Powershell 3 (ca. Juni 2012) tritt die foreachAnweisung jedoch nicht mehr ein $null, sodass der von OP beschriebene Fehler nicht mehr auftritt. Weitere Informationen finden Sie im Abschnitt "ForEach-Anweisung iteriert nicht über $ null" im Powershell-Blogbeitrag Neue Funktionen der V3-Sprache .
Jscott
1

Ich habe eine Lösung entwickelt, indem ich die Abfrage zweimal ausgeführt habe, einmal, um die Dateien abzurufen, und einmal, um die Dateien zu zählen, indem ich get-ChilItem gecastet habe, um ein Array zurückzugeben ($ Backups als Array zu werfen, nachdem die Tatsache nicht zu funktionieren scheint) .
Zumindest funktioniert es wie erwartet (Leistung sollte nicht so problematisch sein, da es nie mehr als ein Dutzend Dateien geben wird). Wenn jemand eine Lösung mit nur einer Abfrage kennt, posten Sie sie bitte.

$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
quelle
1
Ich habe den Beitrag aus Effizienzgründen bearbeitet, da er einfacher war als in den Kommentaren zu erklären. Setzen Sie es einfach zurück, wenn es Ihnen nicht gefällt.
Dan
@ Dan - Doh, ich kann nicht glauben, dass ich das nicht bemerkt habe, danke.
SteB
0

Verwenden Sie Folgendes, um zu bewerten, ob das Array Inhalt enthält:

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

Wenn die Variable nicht vorhanden ist, bewertet Powershell sie einfach als falsch, sodass nicht überprüft werden muss, ob sie vorhanden ist.

Dan
quelle
Durch Hinzufügen von if ($ backups.count -gt 0) wird die Ausführung der Schleife gestoppt, selbst wenn 1 Element in $ backups enthalten ist. $ backups.count gibt auch alleine nichts aus.
SteB
@SteB Ah, ich denke, die Anzahl ist nicht für den Objekttyp implementiert, der die Daten enthält. Ich scanne gelesen und vermutete, dass es sich um ein Array handelt.
Dan
$ count = @ ($ backups) .count; funktioniert fast, aber wenn es keine Dateien gibt, wenn f ($ count -gt 0) wahr ist!
SteB