ป้องกัน foreach loop เมื่อรายการว่าง


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

อย่างไรก็ตามเมื่อการสำรองข้อมูล $ ว่างเปล่าฉันได้รับ: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

ฉันได้พยายาม:

  1. ปกป้องด้านหน้าด้วย 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

คำตอบ:


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-ChildItemcmdlet ที่อยู่ในอาร์เรย์ $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

@SteB คุณถูกต้องตัวอย่างของฉันอธิบายได้ไม่ดี (ยังคงเป็น) แต่ฉันได้ให้การแก้ไขรวมถึงโค้ดตัวอย่างของคุณ สำหรับคำอธิบายเกี่ยวกับพฤติกรรมที่ดีขึ้นโปรดดูโพสต์นี้บนบล็อกของ Keith Hillเขาไม่เพียง แต่เป็นผู้เชี่ยวชาญ PowerShell แต่ยังเป็นนักเขียนที่ดีกว่าฉันด้วย Keith ทำงานใน StackOverflowฉันอยากจะแนะนำให้คุณ (หรือใครก็ตามที่สนใจ PS) ลองดูข้อมูลของเขา
jscott

2

ฉันรู้ว่านี่เป็นโพสต์เก่า แต่ฉันต้องการจะชี้ให้เห็นว่า cmdlet ForEach-Object ไม่ประสบปัญหาเดียวกับการใช้คำสำคัญ 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) foreachคำสั่งจะไม่ก้าวเข้าสู่อีกต่อไป$nullดังนั้นข้อผิดพลาดที่อธิบายโดย OP จะไม่เกิดขึ้นอีก ดูหัวข้อ"คำสั่ง ForEach ไม่ได้ซ้ำไปซ้ำอีก $ null"ใน Powershell Blog post V3 คุณสมบัติภาษาใหม่
jscott

1

ฉันได้พัฒนาวิธีแก้ปัญหาโดยการเรียกใช้แบบสอบถามสองครั้งหนึ่งครั้งเพื่อรับไฟล์และอีกครั้งเพื่อนับจำนวนไฟล์โดยการส่ง get-ChilItem เพื่อส่งกลับอาร์เรย์ .
อย่างน้อยก็ใช้งานได้ตามที่คาดไว้ (ประสิทธิภาพไม่ควรเป็นปัญหาเนื่องจากจะไม่มีไฟล์มากกว่าหนึ่งโหล) หากใครรู้วิธีแก้ปัญหาแบบสอบถามเดียวโปรดโพสต์

$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
ฉันได้แก้ไขโพสต์เพื่อประสิทธิภาพเนื่องจากง่ายกว่าการอธิบายในความคิดเห็น เพียงแค่เปลี่ยนกลับหากคุณไม่ชอบ
ด่าน

@Dan - Doh ไม่อยากจะเชื่อเลยว่าไม่ได้จุดนั้นขอบคุณ
SteB

0

ใช้สิ่งต่อไปนี้เพื่อประเมินว่าอาร์เรย์มีเนื้อหาใด ๆ หรือไม่:

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

หากตัวแปรไม่มีอยู่ Powershell จะประเมินว่าเป็นเท็จดังนั้นไม่จำเป็นต้องตรวจสอบว่ามีอยู่หรือไม่


การเพิ่มถ้า ($ backups.count -gt 0) หยุดลูปที่เรียกใช้งานแม้ว่าจะมี 1 รายการใน $ backups $ backups.count แม้จะเป็นของตัวเองก็ไม่ได้เอาท์พุทอะไรเลย
SteB

@SteB อ้าฉันคิดว่าการนับไม่ถูกนำไปใช้กับประเภทวัตถุใด ๆ ฉันสแกนอ่านและสันนิษฐานว่าเป็นอาเรย์
ด่าน

$ count = @ ($ backups) .count; เกือบใช้งานได้ แต่เมื่อไม่มีไฟล์ถ้า f ($ count -gt 0) เป็นจริง!
SteB
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.