2012-09-05 10 views
8

Estoy tratando de escribir un script que atravesaría 1,6 millones de archivos en una carpeta y moverlos a la carpeta correcta según el nombre del archivo.¿Cómo iterar sobre una carpeta con una gran cantidad de archivos en PowerShell?

La razón es que NTFS no puede manejar un gran número de archivos dentro de una sola carpeta sin una degradación en el rendimiento.

El script llama a "Get-ChildItem" para obtener todos los elementos dentro de esa carpeta, y como es de esperar, esto consume mucha memoria (alrededor de 3.8   GB).

Tengo curiosidad por si hay alguna otra manera de iterar a través de todos los archivos en un directorio sin necesidad de utilizar tanta memoria.

Respuesta

13

Si lo hace

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

que se enfrentará a problemas de memoria.

Uso PowerShell tuberías para procesar los archivos:

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

La segunda manera consumirá menos memoria e idealmente no debería crecer más allá de un cierto punto.

+0

Gracias por la solución agradable y simple. Siempre pensé que el pipeline en PowerShell devolvía el resultado completo antes de procesar la siguiente función. –

+2

Esto realmente todavía requiere memoria 'O (n)', pero si resuelve el problema, entonces estoy de acuerdo en que es la mejor solución. – latkin

12

Si necesita reducir el consumo de memoria, puede omitir el uso de Get-ChildItem y en lugar de utilizar un API .NET directamente. Supongo que está en Powershell v2; si es así, primero siga los pasos here para habilitar .NET 4 para cargar en Powershell v2.

En .NET 4 hay algunas buenas API para enumerando archivos y directorios, en lugar de devolverlos en matrices.

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

Mediante el uso de esta API, en lugar de [IO.Directory]::GetFiles(), sólo un nombre de archivo será procesado a la vez, por lo que el consumo de memoria debería ser relativamente pequeña.

Editar

También estaba asumiendo que había intentado un enfoque segmentado simple como Get-ChildItem |ForEach { process }. Si esto es suficiente, acepto que es el camino a seguir.

Pero quiero aclarar un error común: En v2, Get-ChildItem (o en realidad, el proveedor de sistema de archivos) hace no verdaderamente corriente. La implementación utiliza las API Directory.GetDirectories y Directory.GetFiles, que en su caso generarán una matriz de 1.6M elementos antes de que pueda ocurrir cualquier procesamiento. Una vez hecho esto, entonces sí, el resto de la tubería se está transmitiendo. Y sí, esta pieza inicial de bajo nivel tiene un impacto relativamente mínimo, ya que es simplemente una matriz de cadenas, no una matriz de objetos ricos FileInfo. Pero es incorrecto afirmar que se utiliza la memoria O(1) en este patrón.

Powershell v3, por el contrario, está basado en .NET 4 y, por lo tanto, aprovecha las API de transmisión que mencioné anteriormente (Directory.EnumerateDirectories y Directory.EnumerateFiles). Este es un cambio agradable, y ayuda en escenarios como el tuyo.

+0

Creo que usar Pipeline con Get-ChildItem como manojs sugirió lograr lo mismo, ¡pero gracias por mostrarme cómo usar .Net con PowerShell! :). –

+0

Sí, get-childitem | foreach-objetc {...} también procesará solo un artículo pasado como un tiempo. – x0n

+1

Ver mi edición. 'get-childitem | foreach {...} 'es solo una pseudo-transmisión, técnicamente aún requiere' O (n) 'memoria. – latkin

0

Esta es la forma en que he implementado sin el uso de .Net 4.0. Solo Powershell 2.0 y anticuada DIR-comando:

Es sólo 2 líneas de código (fácil):

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

Mi Powershell Proces solo se aprovecha el 15 MB. ¡Sin cambios en el antiguo servidor de Windows 2008!

¡Salud!

Cuestiones relacionadas