2011-01-22 17 views
10

Tenemos una carpeta en Windows que es ... enorme. Ejecuté "dir> list.txt". El comando perdió la respuesta después de 1,5 horas. El archivo de salida es de aproximadamente 200 MB. Muestra que hay al menos 2,8 millones de archivos. Sé que la situación es estúpida, pero centrémonos en el problema. Si tengo una carpeta así, ¿cómo puedo dividirla en algunas subcarpetas "manejables"? Sorprendentemente, todas las soluciones que he encontrado implican obtener todos los archivos en la carpeta en algún momento, que es un no-no en mi caso. ¿Alguna sugerencia?¿Cómo dividir una gran carpeta?

Gracias Keith Hill and Mehrdad. Acepté la respuesta de Keith porque eso es exactamente lo que quería hacer, pero no pude hacer que PS funcionara rápidamente.

Con la sugerencia de Mehrdad, escribí este pequeño programa. Tomó más de 7 horas mover 2,8 millones de archivos. Entonces el comando dir inicial terminó. Pero de alguna manera no regresó a la consola.

namespace SplitHugeFolder 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var destination = args[1]; 

      if (!Directory.Exists(destination)) 
       Directory.CreateDirectory(destination); 

      var di = new DirectoryInfo(args[0]); 

      var batchCount = int.Parse(args[2]); 
      int currentBatch = 0; 

      string targetFolder = GetNewSubfolder(destination); 

      foreach (var fileInfo in di.EnumerateFiles()) 
      { 
       if (currentBatch == batchCount) 
       { 
        Console.WriteLine("New Batch..."); 
        currentBatch = 0; 
        targetFolder = GetNewSubfolder(destination); 
       } 

       var source = fileInfo.FullName; 
       var target = Path.Combine(targetFolder, fileInfo.Name); 
       File.Move(source, target); 
       currentBatch++; 
      } 
     } 

     private static string GetNewSubfolder(string parent) 
     { 
      string newFolder; 
      do 
      { 
       newFolder = Path.Combine(parent, Path.GetRandomFileName()); 
      } while (Directory.Exists(newFolder)); 
      Directory.CreateDirectory(newFolder); 
      return newFolder; 
     } 
    } 
} 
+0

Uh ... ¿escribir su propia implementación de NTFS y hacer que se divida el árbol de búsqueda binario '$ INDEX_ALLOCATION'? Diviértete ... – Mehrdad

+0

Por cierto, ¿por qué no puedes obtener una lista de todos los archivos? ¿La función 'FindNextFile' también consume tanto tiempo/recursos, o es solo' dir' que hace eso? – Mehrdad

+0

@Mehrdad, porque es demasiado lento. FindNextFile parece prometedor. Intentaremos eso. –

Respuesta

8

Uso Get-ChildItem para indexar todo mi C: unidad cada noche en c: \ filelist.txt. Eso es aproximadamente 580,000 archivos y el tamaño del archivo resultante es ~ 60MB. Es cierto que estoy en Win7 x64 con 8 GB de RAM. Dicho esto, puede intentar algo como esto:

md c:\newdir 
Get-ChildItem C:\hugedir -r | 
    Foreach -Begin {$i = $j = 0} -Process { 
     if ($i++ % 100000 -eq 0) { 
      $dest = "C:\newdir\dir$j" 
      md $dest 
      $j++ 
     } 
     Move-Item $_ $dest 
    } 

La clave es hacer el movimiento de forma continua. Es decir, no recopile todos los resultados de Get-ChildItem en una única variable y luego continúe. Eso requeriría que todos los 2.8 millones de FileInfos estuvieran en la memoria a la vez. Además, si usa el parámetro Name en Get-ChildItem, obtendrá una única cadena que contenga la ruta del archivo relativa al directorio base. Incluso entonces, tal vez este tamaño abrume la memoria disponible para ti. Y sin duda, llevará bastante tiempo ejecutarlo. IIRC correctamente, mi script de indexación toma varias horas.

Si funciona, debería terminar con c:\newdir\dir0 a través de dir28 pero, de nuevo, no he probado esta secuencia de comandos por lo que su kilometraje puede variar. Por cierto, este enfoque asume que tu gran directorio es un directorio bastante plano.

Actualización: El uso del parámetro Name es casi el doble de lento, así que no use ese parámetro.

+0

Esto es lo que quería hacer inicialmente con la salida PS-pipe Get-ChildItem. Otra razón para comenzar a aprender PS. ¡Gracias! –

+0

Y sí, la gran carpeta es plana. Eso es lo que causó el problema en primer lugar. –

0

¿Qué tal comenzar con esto: cmd/c dir/b> list.txt

Eso debe conseguirle una lista de todos los nombres de archivo.

Si está haciendo "dir> list.txt" desde un indicador de PowerShell, get-childitem tiene un alias como "dir". Get-childitem tiene problemas conocidos que enumeran directorios grandes, y las colecciones de objetos que devuelve pueden ser enormes.

+0

No me estaba ejecutando desde PS. Es un simple dir DOS. Murió después de obtener 2.8M archivos. No lo he intentado, pero creo que dir/b funciona de manera similar. –

+0

Devolverá solo los nombres de los archivos.
19.0795682 – mjolinor

+0

(measure-command {cmd/c dir c: \ windows /s}).totalseconds 3.6437911 (measure-command {cmd/c dir c: \ windows/b /s}) .totalseconds 2.6323411 Más rápido, pero no por mucho. – mjolinor

2

Descubrí que el GetChildItem es la opción más lenta al trabajar con muchos elementos en un directorio.

vistazo a los resultados:

Measure-Command { Get-ChildItem C:\Windows -rec | Out-Null } 
TotalSeconds  : 77,3730275 
Measure-Command { listdir C:\Windows | Out-Null } 
TotalSeconds  : 20,4077132 
measure-command { cmd /c dir c:\windows /s /b | out-null } 
TotalSeconds  : 13,8357157 

(con función de listdir define así:

function listdir($dir) { 
    $dir 
    [system.io.directory]::GetFiles($dir) 
    foreach ($d in [system.io.directory]::GetDirectories($dir)) { 
     listdir $d 
    } 
} 

)

Con esto en mente, lo que haría: me alojaría en PowerShell, pero utiliza más enfoque de bajo nivel con.métodos neto:

function DoForFirst($directory, $max, $action) { 
    function go($dir, $options) 
    { 
     foreach ($f in [system.io.Directory]::EnumerateFiles($dir)) 
     { 
      if ($options.Remaining -le 0) { return } 
      & $action $f 
      $options.Remaining-- 
     } 
     foreach ($d in [system.io.directory]::EnumerateDirectories($dir)) 
     { 
      if ($options.Remaining -le 0) { return } 
      go $d $options 
     } 
    } 
    go $directory (New-Object PsObject -Property @{Remaining=$max }) 
} 
doForFirst c:\windows 100 {write-host File: $args } 
# I use PsObject to avoid global variables and ref parameters. 

Para utilizar el código debe de cambiar a tiempo de ejecución .NET 4.0 - métodos de enumeración son nuevos en .NET 4.0.

Puede especificar cualquier scriptblock como parámetro -action, por lo que en su caso sería algo así como {Move-item -literalPath $args -dest c:\dir }.

sólo tratar de enumerar primeros 1000 elementos, espero que va a terminar muy rápidamente:

doForFirst c:\yourdirectory 1000 {write-host '.' -nonew } 

Y, por supuesto, se puede procesar todos los elementos a la vez, sólo tiene que utilizar

doForFirst c:\yourdirectory ([long]::MaxValue) {move-item ... } 

y cada artículo debe procesarse inmediatamente después de su devolución. Entonces, toda la lista no se lee de inmediato y luego se procesa, pero se procesa durante la lectura.

+0

+1 para la comparación del rendimiento! –

+1

Se pone peor. Aproximadamente 300000 archivos, el gráfico de tiempo de resonse va al palo de hockey http://blogs.msdn.com/b/powershell/archive/2009/11/04/why-is-get-childitem-so-slow.aspx – mjolinor

+0

Keep teniendo en cuenta que EnumerateFiles es un nuevo método en .NET 4.0 y normalmente no está disponible para PowerShell. Debe haber modificado su configuración o registro de PowerShell para vincular PowerShell a .NET 4.0. –

Cuestiones relacionadas