Proteggi foreach loop quando elenco vuoto


10

Utilizzando Powershell v2.0 voglio eliminare tutti i file più vecchi di X giorni:

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

Tuttavia, quando $ backup è vuoto ottengo: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

Ho provato:

  1. Proteggere la foreach con if (!$backups)
  2. Proteggere l'elemento Rimuovi con if (Test-Path $file -PathType Leaf)
  3. Proteggere l'elemento Rimuovi con if ([IO.File]::Exists($file.FullName) -ne $true)

Nessuno di questi sembra funzionare, e se il modo raccomandato per impedire l'inserimento di un ciclo foreach se l'elenco fosse vuoto?


@Dan: ho provato entrambi ($ backup> 0) e (@ ($ backup) .count -gt 0), ma nessuno dei due funziona come previsto quando non ci sono file.
SteB

Risposte:


19

Con Powershell 3 l' foreachistruzione non viene ripetuta $nulle il problema descritto da OP non si verifica più.

Dal post del blog di Windows PowerShell Nuove funzionalità della lingua V3 :

Ogni istruzione ForEach non esegue l'iterazione su $ null

In PowerShell V2.0, le persone erano spesso sorprese da:

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

got here

Questa situazione si presenta spesso quando un cmdlet non restituisce alcun oggetto. In PowerShell V3.0, non è necessario aggiungere un'istruzione if per evitare iterazioni su $ null. Ci prendiamo cura di questo per te.

Per PowerShell $PSVersionTable.PSVersion.Major -le 2vedere quanto segue per la risposta originale.


Hai due opzioni, io uso principalmente la seconda.

Controlla $backupsdi no $null. Un semplice Ifgiro del ciclo può controllare se no$null

if ( $backups -ne $null ) {

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

}

O

Inizializza $backupscome array null. Questo evita l'ambiguità del problema di "iterazione di array vuoti" di cui hai chiesto l'ultima domanda .

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

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

Siamo spiacenti, ho dimenticato di fornire un esempio di integrazione del codice. Si noti il Get-ChildItemcmdlet avvolto nella matrice. Ciò funzionerebbe anche con funzioni che potrebbero restituire 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
}

Ho usato il primo (è più facile da capire), non sono riuscito a far funzionare il secondo (probabilmente stavo facendo qualcosa di sbagliato).
SteB

@SteB Hai ragione, il mio esempio è stato spiegato male (lo è ancora), ma ho fornito una modifica che include il tuo codice di esempio. Per una migliore spiegazione del comportamento, consulta questo post sul blog di Keith Hill , non è solo un esperto di PowerShell, ma anche uno scrittore di gran lunga migliore di me. Keith è attivo su StackOverflow , ti incoraggio (o chiunque sia interessato a PS) a dare un'occhiata alle sue cose.
jscott,

2

So che questo è un vecchio post, ma vorrei sottolineare che il cmdlet ForEach-Object non presenta lo stesso problema dell'uso della parola chiave ForEach. Quindi puoi reindirizzare i risultati di DIR a ForEach e fare semplicemente riferimento al file usando $ _, come:

$backups | ForEach{ Remove-Item $_ }

Puoi effettivamente inoltrare il comando Dir stesso attraverso la pipe ed evitare persino di assegnare la variabile come:

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

Ho aggiunto interruzioni di riga per la leggibilità.

Capisco alcune persone come ForEach / In per leggibilità. A volte ogni oggetto ForEach può diventare un po 'peloso, specialmente se stai annidando perché diventa difficile seguire il riferimento $ _. Ad ogni modo, per una piccola operazione come questa è perfetto. Molte persone affermano anche che è più veloce, tuttavia ho scoperto che è solo leggermente.


+1 Ma con Powershell 3 (circa giugno 2012) l' foreachaffermazione non entra più $null, quindi l'errore descritto da OP non si verifica più. Vedi la sezione "Ogni istruzione non scorre oltre $ null" nel post sul blog di Powershell Nuove funzionalità linguistiche V3 .
jscott,

1

Ho sviluppato una soluzione eseguendo la query due volte, una volta per ottenere i file e una volta per contare i file eseguendo il cast di get-ChilItem per restituire un array (il cast di $ backup come array dopo che il fatto sembra non funzionare) .
Almeno funziona come previsto (le prestazioni non dovrebbero essere un problema poiché non ci saranno mai più di una dozzina di file), se qualcuno conosce una soluzione a query singola, si prega di pubblicarla.

$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
Ho modificato il post per efficienza in quanto era più facile che spiegarlo nei commenti. Ripristina se non ti piace.
Dan,

@ Dan - Doh, non posso credere di non averlo notato, grazie.
SteB

0

Utilizzare quanto segue per valutare se l'array ha dei contenuti:

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

Se la variabile non esiste, Powershell la valuterà semplicemente come falsa, quindi non è necessario verificarne l'esistenza.


Aggiungendo se ($ backups.count -gt 0) si interrompe l'esecuzione del ciclo anche quando c'è 1 elemento in $ backup. $ backups.count anche da solo non genera nulla.
SteB

@SteB Ah, immagino che il conteggio non sia implementato per qualsiasi tipo di oggetto contenga i dati. Ho scansionato letto e ho presunto che fosse un array.
Dan,

$ count = @ ($ backup) .count; funziona quasi, ma quando non ci sono file se f ($ count -gt 0) è vero!
SteB
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.