2017-02-21 32 views
2

Я использую пользовательскую функцию, чтобы по существу выполнить команду DIR (рекурсивный список файлов) на диске 8 ТБ (тысячи файлов).Как использовать Pipershell Pipeline, чтобы избежать крупных объектов?

Моей первая итерация была:

$results = $PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime 
$results | Export-CVS -Path $csvfile -Force -Encoding UTF8 -NoTypeInformation -Delimiter "|" 

Это привело к ОГРОМНЫМ $ приводят переменных и замедлял систему вниз к ползанию по пикам процесса Powershell использовать 99% -100% от CPU, как обработка продолжалась.

я решил использовать силу трубопровода для записи в файл CSV непосредственно (предположительно, освобождая память) вместо сохранения в промежуточной переменной, и пришел с этим:

$PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime | ConvertTo-CSV -NoTypeInformation -Delimiter "|" | Out-File -FilePath $csvfile -Force -Encoding UTF8 

Это казалось (файл CSV рос. и процессор казался стабильным), но затем резко остановился, когда размер файла CSV достиг ~ 200 МБ, а ошибка на консоли была «Конвейер был остановлен».

Я не уверен, что размер файла CSV имел какое-либо отношение к сообщению об ошибке, но я не могу обработать этот большой каталог любым способом! Любые предложения о том, как успешно завершить этот процесс?

+2

Есть ли конкретная причина, по которой вы используете 'ConvertTo-Csv | Out-File' вместо 'Export-Csv'? – briantist

+1

Не собирайте все объекты, а затем обрабатывайте их. Вместо этого выходите, как вы идете. –

+1

Может быть [Get-FolderItem] (https://gallery.technet.microsoft.com/scriptcenter/Get-Deeply-Nested-Files-a2148fd7), разбивающийся посередине. Это хорошая работа, но она основана на анализе вывода robocopy. Попробуйте использовать [AlphaFS] (https://github.com/alphaleonis/AlphaFS/wiki/PowerShell) (см. * Пример: Эмуляция Get-ChildItem для преодоления «Слишком длинный путь» *) на связанной странице. – beatcracker

ответ

5

Get-FolderItem запускает robocopy, чтобы перечислить файлы и преобразовать их вывод в массив PSObject. Это медленная операция, которая не требуется для конкретной задачи, строго говоря. Конвейеризация также добавляет большие накладные расходы по сравнению с заявлением . В случае тысяч или сотен тысяч повторений, которые становятся заметными.

Мы можем ускорить процесс за пределами конвейеров, и стандартные командлеты PowerShell могут предлагать для записи информации для 400 000 файлов на SSD-диске за 10 секунд.

  1. .NET Framework 4 или более поздней версии (включена с Win8, устанавливаемая на Win7/XP) IO.DirectoryInfo «s EnumerateFileSystemInfos перечислить файлы в неблокируемой трубопровода как мода;
  2. PowerShell 3 или новее, поскольку он быстрее, чем PS2 в целом;
  3. foreachзаявления, не нужно создавать ScriptBlock контекст для каждого элемента, таким образом, это намного быстрее, чем ForEach командлета
  4. IO.StreamWriter писать данные каждого файл сразу в неблокируемом трубопроводе как мода;
  5. \\?\ prefix trick поднять ограничение длины пути 260 символов;
  6. ручное упорядочение каталогов для обработки ошибок «отказано в доступе», что в противном случае остановило бы наивное перечисление IO.DirectoryInfo;
  7. прогресс отчетности.

function List-PathsInCsv([string[]]$PATHS, [string]$destination) { 
    $prefix = '\\?\' #' UNC prefix lifts 260 character path length restriction 
    $writer = [IO.StreamWriter]::new($destination, $false, [Text.Encoding]::UTF8, 1MB) 
    $writer.WriteLine('Name|Directory|Length|LastWriteTime') 
    $queue = [Collections.Generic.Queue[string]]($PATHS -replace '^', $prefix) 
    $numFiles = 0 

    while ($queue.Count) { 
     $dirInfo = [IO.DirectoryInfo]$queue.Dequeue() 
     try { 
      $dirEnumerator = $dirInfo.EnumerateFileSystemInfos() 
     } catch { 
      Write-Warning ("$_".replace($prefix, '') -replace '^.+?: "(.+?)"$', '$1') 
      continue 
     } 
     $dirName = $dirInfo.FullName.replace($prefix, '') 

     foreach ($entry in $dirEnumerator) { 
      if ($entry -is [IO.FileInfo]) { 
       $writer.WriteLine([string]::Join('|', @(
        $entry.Name 
        $dirName 
        $entry.Length 
        $entry.LastWriteTime 
       ))) 
      } else { 
       $queue.Enqueue($entry.FullName) 
      } 
      if (++$numFiles % 1000 -eq 0) { 
       Write-Progress -activity Digging -status "$numFiles files, $dirName" 
      } 
     } 
    } 
    $writer.Close() 
    Write-Progress -activity Digging -Completed 
} 

Использование:

List-PathsInCsv 'c:\windows', 'd:\foo\bar' 'r:\output.csv' 
+0

Спасибо @wOxxOm. Я попробую рефакторинг и дам вам знать, как это работает! – tresstylez

1

не использовать Robocopy, используйте команду родной PowerShell, например:

$PATHS = 'c:\temp', 'c:\temp2' 
$csvfile='c:\temp\listresult.csv' 

$PATHS | % {Get-ChildItem $_ -file -recurse } | Select Name,DirectoryName,Length,LastWriteTime | export-csv $csvfile -Delimiter '|' -Encoding UTF8 -NoType 

Укороченный вариант для не пуристов:

$PATHS | % {gci $_ -file -rec } | Select Name,DirectoryName,Length,LastWriteTime | epcsv $csvfile -D '|' -E UTF8 -NoT