Lindungi foreach loop ketika daftar kosong

10

Menggunakan Powershell v2.0 Saya ingin menghapus file yang lebih lama dari X hari:

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

Namun, ketika $ backup kosong saya dapatkan: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

Saya sudah mencoba:

  1. Melindungi muka dengan if (!$backups)
  2. Melindungi Remove-Item dengan if (Test-Path $file -PathType Leaf)
  3. Melindungi Remove-Item dengan if ([IO.File]::Exists($file.FullName) -ne $true)

Tak satu pun dari ini tampaknya berfungsi, bagaimana jika cara yang disarankan untuk mencegah loop foreach dimasukkan jika daftar kosong?

SteB
sumber
@Dan - Mencoba keduanya ($ backup> 0) dan (@ ($ backup) .count -gt 0), tetapi tidak berfungsi seperti yang diharapkan ketika tidak ada file.
SteB

Jawaban:

19

Dengan Powershell 3 foreachpernyataan itu tidak berulang $nulldan masalah yang dijelaskan oleh OP tidak lagi terjadi.

Dari posting Blog Windows PowerShell, Fitur Bahasa V3 Baru :

Pernyataan ForEach tidak mengulangi lebih dari $ nol

Di PowerShell V2.0, orang sering dikejutkan oleh:

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

got here

Situasi ini sering muncul ketika cmdlet tidak mengembalikan benda apa pun. Di PowerShell V3.0, Anda tidak perlu menambahkan pernyataan if untuk menghindari iterasi lebih dari $ nol. Kami mengurusnya untuk Anda.

Untuk PowerShell $PSVersionTable.PSVersion.Major -le 2lihat berikut ini untuk jawaban yang asli.


Anda memiliki dua opsi, saya kebanyakan menggunakan yang kedua.

Periksa $backupsuntuk tidak $null. Sederhana di Ifsekitar loop dapat memeriksa tidak$null

if ( $backups -ne $null ) {

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

}

Atau

Inisialisasi $backupssebagai array nol. Ini menghindari ambiguitas masalah "iterate empty array" yang Anda tanyakan dalam pertanyaan terakhir Anda .

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

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

Maaf, saya lalai memberikan contoh mengintegrasikan kode Anda. Perhatikan Get-ChildItemcmdlet yang terbungkus dalam array. Ini juga akan berfungsi dengan fungsi yang dapat mengembalikan 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
sumber
Saya telah menggunakan yang pertama (lebih mudah dimengerti), saya tidak bisa mendapatkan yang kedua bekerja (saya mungkin melakukan sesuatu yang salah).
SteB
@ SteB Anda benar, contoh saya tidak dijelaskan dengan baik (masih), tetapi saya telah memberikan suntingan termasuk kode contoh Anda. Untuk penjelasan perilaku yang lebih baik, silakan lihat posting ini di blog Keith Hill , dia tidak hanya ahli PowerShell, tetapi juga penulis yang jauh lebih baik daripada saya. Keith aktif di StackOverflow , saya akan mendorong Anda (atau siapa pun yang tertarik pada PS) untuk memeriksa barang-barangnya.
jscott
2

Saya tahu ini adalah posting lama tapi saya ingin menunjukkan bahwa cmdlet ForEach-Object tidak mengalami masalah yang sama dengan menggunakan kata kunci ForEach. Jadi, Anda dapat mem-pipe hasil DIR ke ForEach dan hanya mereferensikan file menggunakan $ _, seperti:

$backups | ForEach{ Remove-Item $_ }

Anda benar-benar dapat meneruskan perintah Dir itu sendiri melalui pipa dan menghindari bahkan menetapkan variabel seperti:

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

Saya menambahkan jeda baris untuk keterbacaan.

Saya mengerti beberapa orang seperti ForEach / In untuk keterbacaan. Terkadang ForEach-object bisa sedikit berbulu, terutama jika Anda bersarang karena sulit untuk mengikuti referensi $ _. Bagaimanapun, untuk operasi kecil seperti ini sempurna. Banyak orang juga menyatakan itu lebih cepat, tetapi saya menemukan itu hanya sedikit.

Steven
sumber
+1 Tetapi dengan Powershell 3 (sekitar Juni 2012) foreachpernyataan tidak lagi masuk $null, sehingga kesalahan yang dijelaskan oleh OP tidak lagi terjadi. Lihat bagian "Pernyataan ForEach tidak mengulangi lebih dari $ nol" di posting Blog Powershell Fitur Bahasa V3 Baru .
jscott
1

Saya telah mengembangkan solusi dengan menjalankan query dua kali, sekali untuk mendapatkan file dan sekali untuk menghitung file dengan casting get-ChilItem untuk mengembalikan array (casting $ backup sebagai array setelah fakta tampaknya tidak berfungsi) .
Setidaknya itu berfungsi seperti yang diharapkan (kinerja tidak boleh sebagai masalah karena tidak akan pernah ada lebih dari selusin file), jika ada yang tahu tentang solusi permintaan tunggal, silakan posting itu.

$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
sumber
1
Saya telah mengedit pos untuk efisiensi karena lebih mudah daripada menjelaskan di komentar. Kembalikan saja jika Anda tidak menyukainya.
Dan
@Dan - Doh, tidak percaya saya tidak menemukannya, terima kasih.
SteB
0

Gunakan yang berikut untuk mengevaluasi apakah array memiliki konten:

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

Jika variabel tidak ada, Powershell hanya akan mengevaluasinya sebagai false, jadi tidak perlu memeriksa apakah itu ada.

Dan
sumber
Menambahkan if ($ backups.count -gt 0) menghentikan eksekusi loop bahkan ketika ada 1 item dalam $ backup. $ backups.count bahkan sendiri tidak menghasilkan apa-apa.
SteB
@ SeB Ah, saya kira penghitungan tidak diterapkan untuk jenis objek apa pun yang menyimpan data. Saya memindai membaca dan mengira itu adalah array.
Dan
$ count = @ ($ backups) .count; hampir berfungsi, tetapi ketika tidak ada file jika f ($ count -gt 0) benar!
SteB