Proteger o loop foreach quando lista vazia

10

Usando o Powershell v2.0, desejo excluir todos os arquivos com mais de X dias:

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

No entanto, quando $ backups está vazio, recebo: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

Eu tentei:

  1. Protegendo o foreach com if (!$backups)
  2. Protegendo o item de remoção com if (Test-Path $file -PathType Leaf)
  3. Protegendo o item de remoção com if ([IO.File]::Exists($file.FullName) -ne $true)

Nenhuma delas parece funcionar, e se a maneira recomendada de impedir que um loop foreach seja inserido se a lista estiver vazia?

SteB
fonte
@Dan - Tentei ($ backups> 0) e (@ ($ backups) .count -gt 0), mas não funcionam conforme o esperado quando não há arquivos.
SteB

Respostas:

19

Com o Powershell 3, a foreachinstrução não repete $nulle o problema descrito pelo OP não ocorre mais.

Na postagem do blog do Windows PowerShell, novos recursos de idioma da V3 :

A instrução ForEach não itera mais de $ null

No PowerShell V2.0, as pessoas costumavam se surpreender com:

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

got here

Essa situação geralmente ocorre quando um cmdlet não retorna nenhum objeto. No PowerShell V3.0, você não precisa adicionar uma instrução if para evitar a iteração acima de $ null. Nós cuidamos disso para você.

Para o PowerShell, $PSVersionTable.PSVersion.Major -le 2consulte o seguinte para obter a resposta original.


Você tem duas opções, eu uso principalmente a segunda.

Verifique se $backupsnão $null. Um simples Ifpasso a passo pode verificar se não há$null

if ( $backups -ne $null ) {

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

}

Ou

Inicialize $backupscomo uma matriz nula. Isso evita a ambiguidade do problema "iterar matriz vazia" sobre a qual você perguntou na sua última pergunta .

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

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

Não foi possível fornecer um exemplo de integração do seu código. Observe o Get-ChildItemcmdlet agrupado na matriz. Isso também funcionaria com funções que poderiam retornar 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
fonte
Eu usei o primeiro (é mais fácil de entender), não consegui que o segundo funcionasse (provavelmente estava fazendo algo errado).
STEB
@SteB Você está correto, meu exemplo foi mal explicado (ainda é), mas forneci uma edição incluindo seu código de exemplo. Para uma melhor explicação do comportamento, consulte esta postagem no blog de Keith Hill , ele não é apenas um especialista em PowerShell, mas também um escritor muito melhor do que eu. Keith está ativo no StackOverflow , eu encorajo você (ou qualquer pessoa interessada em PS) a conferir as coisas dele.
jscott
2

Sei que essa é uma postagem antiga, mas gostaria de salientar que o cmdlet ForEach-Object não sofre o mesmo problema que o uso da palavra-chave ForEach. Assim, você pode canalizar os resultados do DIR para o ForEach e apenas referenciar o arquivo usando $ _, como:

$backups | ForEach{ Remove-Item $_ }

Você pode realmente encaminhar o próprio comando Dir através do canal e evitar atribuir a variável como:

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

Eu adicionei quebras de linha para facilitar a leitura.

Entendo que algumas pessoas gostam de ForEach / In para facilitar a leitura. Às vezes, o objeto ForEach pode ficar um pouco peludo, principalmente se você estiver aninhando, pois fica difícil seguir a referência $ _. De qualquer forma, para uma operação pequena como essa, é perfeito. Muitas pessoas também afirmam que é mais rápido, no entanto, descobri que isso é apenas um pouco.

Steven
fonte
+1 Mas, com o Powershell 3 (por volta de junho de 2012), a foreachinstrução não entra mais $null, portanto, o erro descrito pelo OP não ocorre mais. Consulte a seção "A declaração ForEach não itera mais de $ null" na postagem do blog Powershell, Novos recursos de idioma da V3 .
Jscott # 26/15
1

Eu desenvolvi uma solução executando a consulta duas vezes, uma vez para obter os arquivos e outra para contar os arquivos lançando o get-ChilItem para retornar uma matriz (convertendo $ backups como uma matriz depois que o fato parece não funcionar) .
Pelo menos, funciona como esperado (o desempenho não deve ser o problema, pois nunca haverá mais de uma dúzia de arquivos); se alguém souber de uma solução de consulta única, poste-a.

$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
fonte
1
Eu editei o post para obter eficiência, pois era mais fácil do que explicar nos comentários. Apenas reverta-o se você não gostar.
18712 Dan
@ Dan - Doh, não acredito que não vi isso, obrigado.
STEB
0

Use o seguinte para avaliar se a matriz possui algum conteúdo:

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

Se a variável não existir, o Powershell simplesmente a avaliará como falsa, portanto, não é necessário verificar se ela existe.

Dan
fonte
Adicionar if ($ backups.count -gt 0) interrompe a execução do loop mesmo quando há 1 item em $ backups. $ backups.count, por si só, não gera nada.
SteB 13/12/12
@ SteB Ah, acho que a contagem não é implementada para qualquer tipo de objeto que esteja mantendo os dados. Eu digitalizo a leitura e presumi que era uma matriz.
Dan
$ count = @ ($ backups) .count; quase funciona, mas quando não há arquivos, se f ($ count -gt 0) é verdadeiro!
SteB