Proteja el bucle foreach cuando la lista esté vacía

10

Con Powershell v2.0, quiero eliminar cualquier archivo anterior a X días:

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

Sin embargo, cuando $ backups está vacío, obtengo: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

He intentado:

  1. Protegiendo el foreach con if (!$backups)
  2. Protección del elemento de eliminación con if (Test-Path $file -PathType Leaf)
  3. Protección del elemento de eliminación con if ([IO.File]::Exists($file.FullName) -ne $true)

Ninguno de estos parece funcionar, ¿y si la forma recomendada de evitar que se ingrese un bucle foreach si la lista está vacía?

SteB
fuente
@Dan - Intentó ambos ($ backups> 0) y (@ ($ backups) .count -gt 0), pero ninguno de los dos funciona como se esperaba cuando no hay archivos.
SteB

Respuestas:

19

Con Powershell 3, la foreachdeclaración no se repite $nully el problema descrito por OP ya no se produce.

Desde la publicación del blog de Windows PowerShell Nuevas características del lenguaje V3 :

La instrucción ForEach no itera sobre $ null

En PowerShell V2.0, las personas a menudo se sorprendieron por:

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

got here

Esta situación a menudo surge cuando un cmdlet no devuelve ningún objeto. En PowerShell V3.0, no necesita agregar una instrucción if para evitar iterar sobre $ null. Nosotros nos encargamos de eso.

Para PowerShell, $PSVersionTable.PSVersion.Major -le 2consulte lo siguiente para obtener la respuesta original.


Tienes dos opciones, yo uso principalmente la segunda.

Comprueba $backupssi no $null. Una simple Ifvuelta al circuito puede verificar si no$null

if ( $backups -ne $null ) {

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

}

O

Inicializar $backupscomo una matriz nula. Esto evita la ambigüedad del problema "iterar matriz vacía" sobre el que preguntó en su última pregunta .

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

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

Lo sentimos, no proporcioné un ejemplo que integre su código. Tenga en cuenta el Get-ChildItemcmdlet envuelto en la matriz. Esto también funcionaría con funciones que podrían devolver 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
}
revs jscott
fuente
He usado el primero (es más fácil de entender), no pude lograr que el segundo funcionara (probablemente estaba haciendo algo mal).
SteB
@SteB Tienes razón, mi ejemplo fue mal explicado (todavía lo es), pero he proporcionado una edición que incluye tu código de ejemplo. Para una mejor explicación del comportamiento, vea esta publicación en el blog de Keith Hill , no solo es un experto en PowerShell, sino también un escritor mucho mejor que yo. Keith está activo en StackOverflow , te animo (o cualquier persona interesada en PS) a que revises sus cosas.
jscott
2

Sé que esta es una publicación anterior, pero me gustaría señalar que el cmdlet ForEach-Object no sufre el mismo problema que el uso de la palabra clave ForEach. Por lo tanto, puede canalizar los resultados de DIR a ForEach y simplemente hacer referencia al archivo usando $ _, como:

$backups | ForEach{ Remove-Item $_ }

En realidad, puede reenviar el comando Dir a través de la tubería y evitar incluso asignar la variable como:

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

Agregué saltos de línea para facilitar la lectura.

Entiendo que algunas personas les gusta ForEach / In para facilitar la lectura. A veces, ForEach-object puede ponerse un poco peludo, especialmente si está anidando ya que es difícil seguir la referencia $ _. En cualquier caso, para una operación pequeña como esta es perfecta. Muchas personas también afirman que es más rápido, sin embargo, he descubierto que es solo un poco.

Steven
fuente
+1 Pero con Powershell 3 (alrededor de junio de 2012) la foreachdeclaración ya no entra $null, por lo que el error descrito por OP ya no se produce. Consulte la sección "La declaración ForEach no itera sobre $ null" en la publicación del blog Powershell Nuevas características del lenguaje V3 .
jscott
1

Desarrollé una solución ejecutando la consulta dos veces, una para obtener los archivos y otra para contar los archivos mediante la conversión de get-ChilItem para devolver una matriz (convertir $ backups como una matriz después de que el hecho no parece funcionar) .
Al menos funciona como se esperaba (el rendimiento no debería ser tan problemático ya que nunca habrá más de una docena de archivos), si alguien conoce una solución de consulta única, publíquela.

$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
fuente
1
Edité la publicación para mayor eficiencia, ya que era más fácil que explicar en los comentarios. Simplemente revísela si no le gusta.
Dan
@Dan - Doh, no puedo creer que no haya visto eso, gracias.
SteB
0

Use lo siguiente para evaluar si la matriz tiene algún contenido:

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

Si la variable no existe, Powershell simplemente la evaluará como falsa, por lo que no es necesario verificar si existe.

Dan
fuente
Agregar if ($ backups.count -gt 0) detiene la ejecución del ciclo incluso cuando hay 1 elemento en $ backups. $ backups.count incluso por sí solo no genera nada.
SteB
@SteB Ah, supongo que el recuento no se implementa para cualquier tipo de objeto que contenga los datos. Escaneé la lectura y supuse que era una matriz.
Dan
$ cuenta = @ ($ copias de seguridad) .count; casi funciona, pero cuando no hay archivos si f ($ count -gt 0) es verdadero!
SteB