Protéger la boucle foreach lorsque la liste est vide

10

En utilisant Powershell v2.0, je souhaite supprimer tous les fichiers antérieurs à X jours:

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

Cependant, lorsque $ backups est vide, j'obtiens: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

J'ai essayé:

  1. Protéger le foreach avec if (!$backups)
  2. Protection de l'élément supprimé avec if (Test-Path $file -PathType Leaf)
  3. Protection de l'élément supprimé avec if ([IO.File]::Exists($file.FullName) -ne $true)

Rien de tout cela ne semble fonctionner, et si la méthode recommandée pour empêcher l'entrée d'une boucle foreach si la liste était vide?

SteB
la source
@Dan - Testé à la fois ($ backups> 0) et (@ ($ backups) .count -gt 0), mais aucun ne fonctionne comme prévu lorsqu'il n'y a pas de fichiers.
SteB

Réponses:

19

Avec Powershell 3, la foreachdéclaration ne se répète pas $nullet le problème décrit par OP ne se produit plus.

Depuis le blog Windows PowerShell Nouvelles fonctionnalités du langage V3 :

L'instruction ForEach n'itère pas sur $ null

Dans PowerShell V2.0, les gens étaient souvent surpris par:

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

got here

Cette situation se produit souvent lorsqu'une applet de commande ne renvoie aucun objet. Dans PowerShell V3.0, vous n'avez pas besoin d'ajouter une instruction if pour éviter d'itérer sur $ null. Nous nous occupons de cela pour vous.

Pour PowerShell, $PSVersionTable.PSVersion.Major -le 2voir ce qui suit pour la réponse d'origine.


Vous avez deux options, j'utilise principalement la seconde.

Vérifiez $backupsnon $null. Un simple Iftour de boucle peut vérifier non$null

if ( $backups -ne $null ) {

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

}

Ou

Initialiser en $backupstant que tableau nul. Cela évite l'ambiguïté du problème "itérer le tableau vide" que vous avez posé dans votre dernière question .

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

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

Désolé, j'ai négligé de fournir un exemple intégrant votre code. Notez l' Get-ChildItemapplet de commande encapsulée dans le tableau. Cela fonctionnerait également avec des fonctions qui pourraient renvoyer 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
la source
J'ai utilisé le premier (c'est plus facile à comprendre), je n'ai pas réussi à faire fonctionner le second (je faisais probablement quelque chose de mal).
SteB
@SteB Vous avez raison, mon exemple a été mal expliqué (il l'est toujours), mais j'ai fourni une modification incluant votre exemple de code. Pour une meilleure explication du comportement, veuillez consulter cet article sur le blog de Keith Hill , il est non seulement un expert PowerShell, mais aussi un bien meilleur écrivain que moi. Keith est actif sur StackOverflow , je vous encourage (ou toute personne intéressée par PS) à vérifier ses trucs.
jscott
2

Je sais que c'est un ancien article, mais je voudrais souligner que la cmdlet ForEach-Object ne souffre pas du même problème que l'utilisation du mot clé ForEach. Vous pouvez donc diriger les résultats de DIR vers ForEach et simplement référencer le fichier en utilisant $ _, comme:

$backups | ForEach{ Remove-Item $_ }

Vous pouvez réellement transmettre la commande Dir elle-même via le canal et éviter même d'affecter la variable comme:

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

J'ai ajouté des sauts de ligne pour plus de lisibilité.

Je comprends que certaines personnes aiment ForEach / In pour plus de lisibilité. Parfois, ForEach-object peut devenir un peu velu, surtout si vous imbriquez car il devient difficile de suivre la référence $ _. En tout cas, pour une petite opération comme celle-ci, c'est parfait. Beaucoup de gens affirment également que c'est plus rapide, mais j'ai trouvé que ce n'était que légèrement.

Steven
la source
+1 Mais avec Powershell 3 (vers juin 2012), la foreachdéclaration $nulln'intervient plus, donc l'erreur décrite par OP ne se produit plus. Consultez la section «L'instruction ForEach n'itère pas sur $ null» dans le blog Powershell post Nouvelles fonctionnalités du langage V3 .
jscott
1

J'ai développé une solution en exécutant la requête deux fois, une fois pour obtenir les fichiers et une fois pour compter les fichiers en castant get-ChilItem pour renvoyer un tableau (le fait de lancer $ backups en tant que tableau après le fait ne semble pas fonctionner) .
Au moins, cela fonctionne comme prévu (les performances ne devraient pas être aussi problématiques car il n'y aura jamais plus d'une douzaine de fichiers), si quelqu'un connaît une solution à requête unique, veuillez la poster.

$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
la source
1
J'ai édité le message pour plus d'efficacité car c'était plus facile que d'expliquer dans les commentaires. Revenez-le simplement si vous ne l'aimez pas.
Dan
@Dan - Doh, je ne peux pas croire que je n'ai pas remarqué ça, merci.
SteB
0

Utilisez ce qui suit pour évaluer si le tableau a du contenu:

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

Si la variable n'existe pas, Powershell l'évaluera simplement comme fausse, donc pas besoin de vérifier si elle existe.

Dan
la source
L'ajout de if ($ backups.count -gt 0) arrête l'exécution de la boucle même lorsqu'il y a 1 élément dans $ backups. $ backups.count, même seul, ne produit rien.
SteB
@SteB Ah, je suppose que count n'est pas implémenté pour le type d'objet contenant les données. Je scanne la lecture et suppose qu'il s'agit d'un tableau.
Dan
$ count = @ ($ backups) .count; fonctionne presque, mais quand il n'y a pas de fichiers si f ($ count -gt 0) est vrai!
SteB