Bảo vệ vòng lặp foreach khi danh sách trống


10

Sử dụng Powershell v2.0 Tôi muốn xóa bất kỳ tệp nào cũ hơn X ngày:

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

Tuy nhiên, khi sao lưu $ trống, tôi nhận được: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

Tôi đã thử:

  1. Bảo vệ các mục sư với if (!$backups)
  2. Bảo vệ mục Loại bỏ với if (Test-Path $file -PathType Leaf)
  3. Bảo vệ mục Loại bỏ với if ([IO.File]::Exists($file.FullName) -ne $true)

Không ai trong số này dường như hoạt động, điều gì sẽ xảy ra nếu cách ngăn chặn vòng lặp foreach được đề xuất nếu danh sách trống?


@Dan - Đã thử cả ($ sao lưu> 0) và (@ ($ sao lưu) .count -gt 0), nhưng không hoạt động như mong đợi khi không có tệp.
SteB

Câu trả lời:


19

Với Powershell 3, foreachcâu lệnh không lặp lại $nullvà vấn đề được mô tả bởi OP không còn xảy ra nữa.

Từ bài viết trên Windows PowerShell Blog Các tính năng ngôn ngữ V3 mới :

Câu lệnh ForEach không lặp lại trên $ null

Trong PowerShell V2.0, mọi người thường ngạc nhiên bởi:

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

got here

Tình huống này thường xảy ra khi một lệnh ghép ngắn không trả về bất kỳ đối tượng nào. Trong PowerShell V3.0, bạn không cần thêm câu lệnh if để tránh lặp lại trên $ null. Chúng tôi chăm sóc điều đó cho bạn.

Đối với PowerShell, $PSVersionTable.PSVersion.Major -le 2xem phần sau đây để biết câu trả lời gốc.


Bạn có hai lựa chọn, tôi chủ yếu sử dụng thứ hai.

Kiểm tra $backupskhông $null. Một đơn giản Ifxung quanh vòng lặp có thể kiểm tra không$null

if ( $backups -ne $null ) {

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

}

Hoặc là

Khởi tạo $backupsnhư một mảng null. Điều này tránh sự mơ hồ về vấn đề "lặp lại mảng trống" mà bạn đã hỏi trong câu hỏi cuối cùng .

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

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

Xin lỗi, tôi đã bỏ qua việc cung cấp một ví dụ tích hợp mã của bạn. Lưu ý Get-ChildItemlệnh ghép được bọc trong mảng. Điều này cũng sẽ làm việc với các chức năng có thể trả về 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
}

Tôi đã sử dụng cái thứ nhất (dễ hiểu hơn), tôi không thể làm cái thứ hai hoạt động (có lẽ tôi đã làm gì đó sai).
SteB

@SteB Bạn đã đúng, ví dụ của tôi được giải thích kém (vẫn còn), nhưng tôi đã cung cấp một chỉnh sửa bao gồm mã ví dụ của bạn. Để giải thích rõ hơn về hành vi, vui lòng xem bài đăng này trên blog của Keith Hill , anh ấy không chỉ là chuyên gia về PowerShell, mà còn là một nhà văn giỏi hơn tôi rất nhiều. Keith đang hoạt động trên StackOverflow , tôi sẽ khuyến khích bạn (hoặc bất kỳ ai quan tâm đến PS) kiểm tra nội dung của anh ấy.
jscott

2

Tôi biết đây là một bài viết cũ nhưng tôi muốn chỉ ra rằng lệnh ghép ngắn ForEach-Object không gặp phải vấn đề tương tự như khi sử dụng từ khóa ForEach. Vì vậy, bạn có thể chuyển các kết quả của DIR sang ForEach và chỉ cần tham chiếu tệp bằng $ _, như:

$backups | ForEach{ Remove-Item $_ }

Bạn thực sự có thể chuyển tiếp lệnh Dir thông qua đường ống và thậm chí tránh gán biến như:

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

Tôi đã thêm ngắt dòng cho dễ đọc.

Tôi hiểu một số người như ForEach / In để dễ đọc. Đôi khi, đối tượng ForEach có thể có một ít lông, đặc biệt nếu bạn đang làm tổ vì khó theo dõi tham chiếu $ _. Ở mức độ nào, đối với một hoạt động nhỏ như thế này là hoàn hảo. Nhiều người cũng khẳng định nó nhanh hơn tuy nhiên tôi thấy rằng chỉ một chút thôi.


+1 Nhưng với Powershell 3 (khoảng tháng 6 năm 2012), foreachcâu lệnh không còn bước vào nữa $null, do đó lỗi được mô tả bởi OP không còn xảy ra nữa. Xem phần "Tuyên bố ForEach không lặp lại trên $ null" trong bài đăng trên Blog của Powershell Các tính năng ngôn ngữ V3 mới .
jscott

1

Tôi đã phát triển một giải pháp bằng cách chạy truy vấn hai lần, một lần để lấy các tệp và một lần để đếm các tệp bằng cách truyền get-ChilItem để trả về một mảng (truyền các bản sao lưu $ thành một mảng sau khi thực tế dường như không hoạt động) .
Ít nhất là nó hoạt động như mong đợi (hiệu suất không phải là vấn đề vì sẽ không bao giờ có hơn một chục tệp), nếu có ai biết về một giải pháp truy vấn duy nhất, vui lòng đăng nó.

$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
Tôi đã chỉnh sửa bài đăng cho hiệu quả vì nó dễ hơn là giải thích trong các bình luận. Chỉ cần hoàn nguyên nó nếu bạn không thích nó.
Dan

@Dan - Doh, không thể tin rằng tôi đã không phát hiện ra điều đó, cảm ơn.
SteB

0

Sử dụng như sau để đánh giá xem mảng có bất kỳ nội dung nào không:

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

Nếu biến không tồn tại, Powershell sẽ chỉ đánh giá nó là sai, vì vậy không cần kiểm tra xem nó có tồn tại không.


Việc thêm if ($ backups.count -gt 0) dừng thực thi vòng lặp ngay cả khi có 1 mục trong $ backup. $ backups.count ngay cả trên chính nó không tạo ra bất cứ điều gì.
SteB

@SteB Ah, tôi đoán số lượng không được triển khai cho bất kỳ loại đối tượng nào đang giữ dữ liệu. Tôi quét đọc và cho rằng đó là một mảng.
Dan

$ đếm = @ ($ sao lưu) .count; gần như hoạt động, nhưng khi không có tệp nếu f ($ đếm -gt 0) là đúng!
SteB
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.