空列表时保护foreach循环


10

我想使用Powershell v2.0删除X天之前的所有文件:

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

但是,当$ backups为空时,我得到: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

我试过了:

  1. 通过以下方式保护foreach if (!$backups)
  2. 使用以下方法保护删除项 if (Test-Path $file -PathType Leaf)
  3. 使用以下方法保护删除项 if ([IO.File]::Exists($file.FullName) -ne $true)

这些似乎都不起作用,如果建议的方法是在列表为空的情况下阻止输入foreach循环,该怎么办?


@Dan-尝试了($ backups> 0)和(@($ backups.count -gt 0),但是在没有文件时都无法按预期工作。
SteB 2012年

Answers:


19

使用Powershell 3时,该foreach语句不会重复进行,$null并且OP所描述的问题不再发生。

Windows PowerShell博客文章中,新的V3语言功能

ForEach语句不会遍历$ null

在PowerShell V2.0中,人们常常对以下方面感到惊讶:

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

got here

当cmdlet不返回任何对象时,通常会出现这种情况。在PowerShell V3.0中,您无需添加if语句来避免迭代$ null。我们会为您服务。

对于PowerShell,$PSVersionTable.PSVersion.Major -le 2请参阅以下原始答案。


您有两个选择,我主要使用第二个。

检查$backups是否没有$null。一个简单If的循环可以检查是否$null

if ( $backups -ne $null ) {

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

}

要么

初始化$backups为空数组。这避免了您在上一个问题中提出的“迭代空数组”问题的歧义。

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

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

抱歉,我没有提供集成您的代码的示例。请注意Get-ChildItem包装在数组中的cmdlet。这也可以与可能返回的函数一起使用$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
}

我用了第一个(更容易理解),我无法使用第二个(我可能做错了)。
SteB 2012年

@SteB您是正确的,我的示例没有得到很好的解释(它仍然是),但是我提供了包含您的示例代码的编辑。有关行为的更好解释,请参阅Keith Hill博客上的这篇文章,他不仅是PowerShell专家,而且还是一位比我更好的作家。 Keith活跃于StackOverflow,我鼓励您(或对PS感兴趣的任何人)检查他的东西。
jscott 2012年

2

我知道这是一篇旧文章,但我想指出,ForEach-Object cmdlet不会像使用ForEach关键字那样遭受相同的问题。因此,您可以将DIR的结果通过管道传递到ForEach并仅使用$ _引用文件,例如:

$backups | ForEach{ Remove-Item $_ }

您实际上可以通过管道转发Dir命令本身,甚至可以避免分配变量,例如:

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

我添加了换行符以提高可读性。

我了解一些人喜欢ForEach / In的可读性。有时,ForEach-object可能会有些毛茸茸,尤其是在嵌套时,因为很难遵循$ _引用。无论如何,对于这样的小操作,它是完美的。许多人还断言它的速度更快,但是我发现它只是略有增加。


+1但是使用Powershell 3(大约在2012年6月)时,该foreach语句不再进入$null,因此不再出现OP描述的错误。请参阅Powershell博客文章New V3 Language Features中的“ ForEach语句不会在$ null上迭代”部分。
jscott 2015年

1

我已经通过运行两次查询来开发一种解决方案,一次是通过获取get-ChilItem来返回文件,一次是通过对get-ChilItem的返回计数来计数文件(在事实似乎不起作用之后将$ backups作为数组进行广播) 。
至少它能按预期工作(性能不会成为问题,因为不会有十几个文件),如果有人知道单查询解决方案,请发布它。

$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
我已编辑帖子以提高效率,因为它比评论中的解释要容易。如果您不喜欢它,只需还原它。
2012年

@Dan-Doh,不敢相信我没有发现这一点,谢谢。
SteB 2012年

0

使用以下命令评估数组是否包含任何内容:

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

如果该变量不存在,Powershell会简单地将其评估为false,因此无需检查它是否存在。


添加if($ backups.count -gt 0)会停止循环执行,即使$ backups中有1个项目也是如此。即使单独运行$ backups.count也不会输出任何内容。
SteB 2012年

@SteB啊,我想对于保存数据的任何对象类型都没有实现计数。我扫描读取,并假定它是一个数组。
2012年

$ count = @($ backups).count; 几乎可以使用,但是如果f($ count -gt 0)为true,则在没有文件的情况下!
SteB 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.