2012-09-05 5 views
8

Ich versuche ein Skript zu schreiben, das 1,6 Millionen Dateien in einem Ordner durchläuft und sie basierend auf dem Dateinamen in den richtigen Ordner verschiebt.Wie wird über einen Ordner mit einer großen Anzahl von Dateien in PowerShell iteriert?

Der Grund ist, dass NTFS eine große Anzahl von Dateien in einem einzelnen Ordner nicht ohne Beeinträchtigung der Leistung verarbeiten kann.

Der Skriptaufruf "Get-ChildItem", um alle Elemente in diesem Ordner zu erhalten, und wie Sie vielleicht erwarten, verbraucht dies viel Arbeitsspeicher (etwa 3,8   GB).

Ich bin gespannt, ob es andere Möglichkeiten gibt, alle Dateien in einem Verzeichnis zu durchlaufen, ohne so viel Speicher zu verbrauchen.

Antwort

13

Wenn Sie

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

tun, werden Sie Speicherprobleme stellen.

Verwenden Powershell Leitung, um die Dateien zu verarbeiten:

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

Der zweite Weg wird weniger Speicher verbrauchen und sollte idealerweise nicht über einen gewissen Punkt wachsen.

+0

Danke für die nette und einfache Lösung. Ich hatte immer gedacht, dass Pipelining in Powershell das gesamte Ergebnis vor der Verarbeitung der nächsten Funktion zurückgibt. –

+2

Dies erfordert tatsächlich noch 'O (n)' Speicher, aber wenn es das Problem löst, dann stimme ich zu, es ist die beste Lösung. – latkin

12

Wenn Sie den Speicherbedarf reduzieren müssen, können Sie die Verwendung von Get-ChildItem überspringen und stattdessen direkt eine .NET API verwenden. Ich gehe davon aus, dass Sie Powershell v2 verwenden. Wenn dies der Fall ist, befolgen Sie die Schritte here, um .NET 4 zum Laden in Powershell v2 zu aktivieren.

In .NET 4 gibt es einige nette APIs für Enumeration Dateien und Verzeichnisse, im Gegensatz zu ihnen in Arrays zurückgeben.

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

Durch diese API verwendet, statt [IO.Directory]::GetFiles(), wird nur ein Dateiname zu einem Zeitpunkt verarbeitet werden, so sollte der Speicherverbrauch relativ klein sein.

bearbeiten

ich auch annahm Sie einen einfachen Pipeline-Ansatz wie Get-ChildItem |ForEach { process } versucht hatte. Wenn das genug ist, stimme ich zu, dass es der richtige Weg ist.

Aber ich will ein weit verbreitetes Missverständnis klären: In v2, Get-ChildItem (oder wirklich, die Filesystem-Anbieter) tun nicht wirklich streamen. Die Implementierung verwendet die APIs Directory.GetDirectories und Directory.GetFiles, die in Ihrem Fall ein Array mit 1,6M-Elementen generieren, bevor eine Verarbeitung stattfinden kann. Sobald dies geschehen ist, ja, der Rest der Pipeline streamt. Und ja, diese anfängliche Low-Level-Stück hat relativ minimale Auswirkungen, da es einfach ein String-Array, nicht eine Reihe von reichen Objekten ist. Aber es ist falsch zu behaupten, dass O(1) Speicher in diesem Muster verwendet wird.

Powershell v3 hingegen ist auf .NET 4 aufgebaut und nutzt daher die oben erwähnten Streaming-APIs (Directory.EnumerateDirectories und Directory.EnumerateFiles). Das ist eine nette Abwechslung und hilft in Szenarien wie deiner.

+0

Ich denke, die Verwendung von Pipeline mit Get-ChildItem, wie manojlds vorgeschlagen hatte, würde das Gleiche erreichen, aber danke, dass Sie mir gezeigt haben, wie man .Net mit Powershell verwendet! :). –

+0

Ja, hol dein Kind! foreach-objetc {...} verarbeitet auch nur einen übergebenen Gegenstand als Zeit. – x0n

+1

Siehe meine Bearbeitung. 'get-childitem | foreach {...} 'ist nur Pseudo-Streaming, es benötigt technisch immer noch' O (n) 'Speicher. – latkin

0

So habe ich es ohne .Net 4.0 implementiert. Nur Powershell 2.0 und altmodischen DIR-Befehl:

Es ist nur 2 Zeilen (leicht) Code:

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

Mein Powershell Proces verwendet nur 15 MB. Keine Änderungen auf dem alten Windows 2008 Server!

Prost!

Verwandte Themen