2012-09-05 30 views
8

Tôi đang cố gắng viết một tập lệnh có thể đi qua 1,6 triệu tệp trong một thư mục và di chuyển chúng vào đúng thư mục dựa trên tên tệp.Làm thế nào để lặp qua một thư mục với một số lượng lớn các tệp trong PowerShell?

Lý do là NTFS không thể xử lý một số lượng lớn tệp trong một thư mục mà không làm giảm hiệu suất.

Tập lệnh gọi "Get-ChildItem" để nhận tất cả các mục trong thư mục đó và như bạn có thể mong đợi, điều này tiêu tốn rất nhiều bộ nhớ (khoảng 3.8   GB).

Tôi tò mò nếu có bất kỳ cách nào khác để lặp qua tất cả các tệp trong thư mục mà không cần sử dụng quá nhiều bộ nhớ.

Trả lời

13

Nếu bạn làm

$files = Get-ChildItem $dirWithMillionsOfFiles 
#Now, process with $files 

bạn sẽ phải đối mặt các vấn đề bộ nhớ.

Sử dụng PowerShell đường ống để xử lý các tập tin:

Get-ChildItem $dirWithMillionsOfFiles | %{ 
    #process here 
} 

Cách thứ hai sẽ tiêu thụ ít bộ nhớ và lý tưởng nên không phát triển vượt ra ngoài một thời điểm nào đó.

+0

Cảm ơn vì giải pháp đơn giản và tốt đẹp. Tôi đã luôn luôn nghĩ pipelining trong PowerShell trả lại toàn bộ kết quả trước khi xử lý chức năng tiếp theo. –

+2

Điều này thực sự vẫn đòi hỏi bộ nhớ 'O (n)', nhưng nếu nó giải quyết được vấn đề thì tôi đồng ý đó là giải pháp tốt nhất. – latkin

12

Nếu bạn cần giảm dung lượng bộ nhớ, bạn có thể bỏ qua sử dụng Get-ChildItem và thay vào đó sử dụng .NET API trực tiếp. Tôi giả sử bạn đang ở trên Powershell v2, nếu như vậy đầu tiên hãy làm theo các bước here để bật .NET 4 để tải trong Powershell v2.

Trong .NET 4, có một số API tốt đẹp để liệt kê tệp và thư mục, thay vì trả lại chúng trong mảng.

[IO.Directory]::EnumerateFiles("C:\logs") |%{ <move file $_> } 

Bằng cách sử dụng API này, thay vì [IO.Directory]::GetFiles(), chỉ có một tên tập tin sẽ được xử lý tại một thời điểm, vì vậy mức tiêu thụ bộ nhớ nên tương đối nhỏ.

Sửa

Tôi cũng giả sử bạn đã cố gắng một cách tiếp cận đơn giản như pipelined Get-ChildItem |ForEach { process }. Nếu điều này là đủ, tôi đồng ý đó là con đường để đi.

Nhưng tôi muốn làm sáng tỏ quan niệm sai lầm phổ biến: Trong v2, Get-ChildItem (hoặc thực sự, nhà cung cấp Hệ thống tệp) thực hiện không phải là luồng thực sự. Việc triển khai sử dụng các API Directory.GetDirectoriesDirectory.GetFiles, trong trường hợp của bạn sẽ tạo ra một mảng yếu tố 1,6M trước khi bất kỳ quá trình xử lý nào có thể xảy ra. Khi điều này được thực hiện, thì có, phần còn lại của kênh đang phát trực tuyến. Và có, đoạn ban đầu thấp này có tác động tương đối tối thiểu, vì nó chỉ đơn giản là một mảng chuỗi, không phải là một mảng các đối tượng phong phú FileInfo. Nhưng không đúng khi tuyên bố rằng bộ nhớ O(1) được sử dụng trong mẫu này.

Ngược lại, Powershell v3 được xây dựng trên .NET 4 và do đó tận dụng các API phát trực tiếp mà tôi đề cập ở trên (Directory.EnumerateDirectoriesDirectory.EnumerateFiles). Đây là một thay đổi tốt đẹp, và giúp trong các tình huống giống như của bạn.

+0

Tôi nghĩ rằng việc sử dụng đường dẫn với Get-ChildItem như manojlds đã gợi ý sẽ đạt được điều tương tự, nhưng cảm ơn vì đã chỉ cho tôi cách sử dụng .Net với powershell! :). –

+0

Yep, get-childitem | foreach-objetc {...} cũng sẽ chỉ xử lý một mục đã qua dưới dạng thời gian. – x0n

+1

Xem chỉnh sửa của tôi. 'get-childitem | foreach {...} 'chỉ là giả streaming, về mặt kỹ thuật nó vẫn yêu cầu bộ nhớ' O (n) '. – latkin

0

Đây là cách tôi triển khai nó mà không cần sử dụng .Net 4.0. Chỉ Powershell 2.0 và cổ hủ DIR-lệnh:

Nó chỉ là 2 dòng (dễ) mã:

cd <source_path> 
cmd /c "dir /B"| % { move-item $($_) -destination "<dest_folder>" } 

My Powershell Process chỉ sử dụng 15MB. Không có thay đổi trên máy chủ Windows 2008 cũ!

Chúc mừng!

Các vấn đề liên quan