Как разбить огромную папку? - PullRequest
10 голосов
/ 22 января 2011

У нас есть папка в Windows ... огромная. Я запустил "dir> list.txt". Команда потеряла ответ через 1,5 часа. Выходной файл составляет около 200 МБ. Это показывает, что там по крайней мере 2,8 миллиона файлов. Я знаю, что ситуация глупая, но давайте сосредоточимся на самой проблеме. Если у меня есть такая папка, как я могу разделить ее на несколько «управляемых» подпапок? Удивительно, но все решения, которые я придумала, включают в себя получение всех файлов в папке в какой-то момент, что является нет-нет в моем случае. Есть предложения?

Спасибо Кит Хилл и Мехрдад. Я принял ответ Кита, потому что это именно то, что я хотел сделать, но я не мог заставить PS работать быстро.

С советом Мехрдада я написал эту маленькую программу. Чтобы переместить 2,8 миллиона файлов, потребовалось 7+ часов. Итак, начальная команда dir завершилась. Но почему-то это не вернулось к консоли.

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;
        }
    }
}

Ответы [ 3 ]

8 голосов
/ 22 января 2011

Я использую Get-ChildItem, чтобы каждую ночь индексировать весь диск C: в файл c: \ filelist.txt.Это около 580 000 файлов, и в результате размер файла составляет ~ 60 МБ.По общему признанию я на Win7 x64 с 8 ГБ ОЗУ.Тем не менее, вы можете попробовать что-то вроде этого:

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 
    }

Ключ заключается в том, чтобы сделать движение в потоковом режиме.То есть не собирайте все результаты Get-ChildItem в одну переменную, а затем продолжайте.Это потребовало бы, чтобы все 2,8 миллиона FileInfos были в памяти одновременно.Кроме того, если вы используете параметр Name в Get-ChildItem, он выведет одну строку, содержащую путь файла относительно базового каталога.Даже тогда, возможно, этот размер просто сокрушит доступную вам память.И без сомнения, это займет довольно много времени, чтобы выполнить.IIRC правильно, мой скрипт индексации занимает несколько часов.

Если он работает, вы должны получить c:\newdir\dir0 thru dir28, но опять же, я вообще не тестировал этот скрипт, поэтому ваш пробег можетварьироваться.Кстати, этот подход предполагает, что ваш огромный каталог - довольно плоский каталог.

Обновление: Использование параметра Name почти в два раза медленнее, поэтому не используйте этот параметр.

2 голосов
/ 22 января 2011

Я обнаружил, что GetChildItem - самый медленный параметр при работе со многими элементами в каталоге.

Посмотрите на результаты:

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

(с функцией listdir, определенной следующим образом:

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

)

Имея это в виду, я бы сделал следующее: я бы остался в PowerShell, но использовал бы более низкоуровневый подход с методами .NET:

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.

Чтобы использовать код, вам нужно переключиться на среду выполнения .NET 4.0 - методы перечисления являются новыми в .NET 4.0.

Вы можете указать любой блок скриптов как параметр -action, поэтому вв вашем случае это будет что-то вроде {Move-item -literalPath $args -dest c:\dir }.

Просто попробуйте перечислить первые 1000 элементов, я надеюсь, что это закончится очень быстро:

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

И, конечно, вы можете обработать все элементысразу используйте

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

, и каждый элемент должен быть обработан сразу после его возврата.Таким образом, весь список не читается сразу, а затем обрабатывается, но обрабатывается во время чтения.

0 голосов
/ 22 января 2011

Как насчет начать с этого: cmd / c dir / b> list.txt

Это должно дать вам список всех имен файлов.

Если вы используете "dir> list.txt" из командной строки powershell, get-childitem будет иметь псевдоним "dir". У Get-childitem есть известные проблемы, перечисляющие большие каталоги, и коллекции объектов, которые он возвращает, могут стать огромными.

...