2011-01-22 37 views
10

Mamy folder w systemie Windows, który jest ... ogromny. Uruchomiłem "dir> list.txt". Komenda straciła odpowiedź po 1,5 godziny. Plik wyjściowy ma około 200 MB. Pokazuje, że istnieje co najmniej 2,8 miliona plików. Wiem, że sytuacja jest głupia, ale skupmy się na samym problemie. Jeśli mam taki folder, w jaki sposób mogę go podzielić na "podrzędne foldery" z możliwością zarządzania? Zaskakujące jest to, że wszystkie rozwiązania, które wymyśliłem, polegają na pobieraniu wszystkich plików w folderze, co jest w moim przypadku nieuniknione. Jakieś sugestie?Jak podzielić duży folder?

Dziękuję Keithowi Hillowi i Mehrdadzie. Przyjąłem odpowiedź Keitha, ponieważ dokładnie to chciałem zrobić, ale nie mogłem szybko przekonać PS.

Z napiwkiem Mehrdada napisałem ten mały program. Przesłanie 2,8 miliona plików zajęło ponad 7 godzin. Tak więc pierwsza komenda dir zakończyła się. Ale jakoś nie wróciło do konsoli.

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 ... napisz własną implementację systemu NTFS i podziel go drzewo binarne "$ INDEX_ALLOCATION"? Miłej zabawy ... – Mehrdad

+0

Nawiasem mówiąc, dlaczego nie możesz uzyskać listy wszystkich plików? Czy funkcja 'FindNextFile' zużywa tak dużo czasu/zasobów, czy jest to po prostu' dir', które to robi? – Mehrdad

+0

@Mebrad, ponieważ jest zbyt wolny. FindNextFile wydaje się obiecujący. Spróbuję tego. –

Odpowiedz

8

Używam funkcji Get-ChildItem do indeksowania całego dysku C: co noc do pliku c: \ filelist.txt. To około 580 000 plików, a wynikowy rozmiar pliku wynosi ~ 60 MB. Wprawdzie jestem na Win7 x64 z 8 GB pamięci RAM. Powiedział, że możesz spróbować czegoś takiego:

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 
    } 

Kluczem jest, aby wykonać ruch w sposób streamingu. Oznacza to, że nie gromadzą wszystkich wyników Get-ChildItem w jednej zmiennej, a następnie kontynuują. To wymagałoby natychmiastowego załadowania do pamięci wszystkich 2,8 miliona plików FileInfos. Ponadto, jeśli użyjesz parametru Name na Get-ChildItem, wyświetli on pojedynczy ciąg zawierający ścieżkę pliku względem katalogu podstawowego. Nawet wtedy, być może ten rozmiar po prostu przytłoczy pamięć dostępną dla ciebie. Bez wątpienia zajmie to sporo czasu. IIRC poprawnie, mój skrypt indeksujący zajmuje kilka godzin.

Jeśli to zadziała, powinieneś skończyć z c:\newdir\dir0 przez dir28, ale potem znowu, nie testowałem tego skryptu, więc twój przebieg może się różnić. BTW w tym podejściu zakłada, że ​​jesteś ogromny reżim jest dość płaskim reż.

Aktualizacja: Za pomocą parametru Name jest prawie dwukrotnie wolno więc nie korzystać z tego parametru.

+0

To jest to, co chciałem najpierw zrobić z wyjściem PS - pipe Get-ChildItem. Kolejny powód, aby rozpocząć naukę PS. Dzięki! –

+0

I tak, ogromny folder jest płaski. To właśnie spowodowało problem. –

0

Jak o rozpoczęciu z tym: cmd/c dir/b> lista.txt

To powinno Ci listę wszystkich nazw plików.

Jeśli robisz "dir> list.txt" z wiersza poleceń, get-childitem jest aliasowany jako "dir". Get-childitem ma znane problemy z wyliczaniem dużych katalogów, a kolekcje obiektów, które zwraca, mogą być ogromne.

+0

Nie byłem uruchomiony z PS. To prosty DOS reż. Zginął po uzyskaniu plików 2.8M. Nie próbowałem, ale domyślam się, że dir/b działa podobnie. –

+0

Zwróci tylko nazwy plików.
19.0795682 – mjolinor

+0

(miara-polecenie {cmd/c katalogowy c: \ windows /s}).totalseconds 3.6437911 (polecenie-miary {cmd/c katalogowy: \ windows/b /s}).totalseconds 2.6323411 Szybszy, ale nie przez dużo. – mjolinor

2

Dowiedziałem się, że GetChildItem jest najwolniejszą opcją podczas pracy z wieloma pozycjami w katalogu.

spojrzeć na wyniki:

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 

(z funkcją listdir zdefiniowane następująco:

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

)

Mając to na uwadze, co zrobię: Chciałbym pozostać w PowerShell, ale użyj więcej niż niskiego poziomu podejścia.NET metody:

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. 

Aby użyć kodu trzeba przełączyć się na .NET 4.0 Runtime - metody wyliczania są nowe w .NET 4.0.

Można określić dowolny blok skryptów jako parametr -action, więc w twoim przypadku będzie to coś w rodzaju {Move-item -literalPath $args -dest c:\dir }.

Spróbuj do listy pierwszych 1000 elementów, mam nadzieję, że będzie to skończyć bardzo szybko:

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

i oczywiście można przetworzyć wszystkich elementów naraz, wystarczy użyć

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

i każda pozycja powinny być przetwarzane natychmiast po ich zwróceniu. Tak więc cała lista nie jest od razu czytana, a następnie przetwarzana, ale przetwarzana podczas czytania.

+0

+1 do porównania wydajności! –

+1

Gorzej. Przy około 300 000 plików wykres czasu odpowiedzi wyświetla hokejowy kij http://blogs.msdn.com/b/powershell/archive/2009/11/04/why-is-get-childitem-so-slow.aspx – mjolinor

+0

Zachowaj pamiętając, że EnumerateFiles jest nową metodą w .NET 4.0 i zazwyczaj niedostępną dla PowerShell. Konieczne było zmodyfikowanie konfiguracji lub rejestru programu PowerShell w celu powiązania PowerShell z .NET 4.0. –