Преобразовать текстовый файл с фиксированной шириной столбца 4 ГБ без разделителей и более 100 столбцов в обрезанный файл с разделителями табуляции - PullRequest
1 голос
/ 08 ноября 2019

Ежемесячно я получаю несколько очень больших (~ 4 ГБ) текстовых файлов с фиксированной шириной столбца, которые необходимо импортировать в MS SQL Server. Чтобы импортировать файл, его необходимо преобразовать в текстовый файл со значениями столбцов, разделенных табуляцией, с пробелами, обрезанными по значению каждого столбца (в некоторых столбцах нет пробелов). Я хотел бы использовать PowerShell для решения этой проблемы, и я хотел бы, чтобы код был очень, очень быстрым.

Я пробовал много итераций кода, но пока слишком медленно или не работает. Я пробовал Microsoft Text Parser (слишком медленно). Я пытался сопоставить регулярные выражения. Я работаю на компьютере с Windows 7 с установленным PowerShell 5.1.

 ID         FIRST_NAME              LAST_NAME          COLUMN_NM_TOO_LON5THCOLUMN
 10000000001MINNIE                  MOUSE              COLUMN VALUE LONGSTARTS 

$infile = "C:\Testing\IN_AND_OUT_FILES\srctst.txt"
$outfile = "C:\Testing\IN_AND_OUT_FILES\outtst.txt"

$batch = 1

[regex]$match_regex = '^(.{10})(.{50})(.{50})(.{50})(.{50})(.{3})(.{8})(.{4})(.{50})(.{2})(.{30})(.{6})(.{3})(.{4})(.{25})(.{2})(.{10})(.{3})(.{8})(.{4})(.{50})(.{2})(.{30})(.{6})(.{3})(.{2})(.{25})(.{2})(.{10})(.{3})(.{10})(.{10})(.{10})(.{2})(.{10})(.{50})(.{50})(.{50})(.{50})(.{8})(.{4})(.{50})(.{2})(.{30})(.{6})(.{3})(.{2})(.{25})(.{2})(.{10})(.{3})(.{4})(.{2})(.{4})(.{10})(.{38})(.{38})(.{15})(.{1})(.{10})(.{2})(.{10})(.{10})(.{10})(.{10})(.{38})(.{38})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})(.{10})$'
[regex]$replace_regex = "`${1}`t`${2}`t`${3}`t`${4}`t`${5}`t`${6}`t`${7}`t`${8}`t`${9}`t`${10}`t`${11}`t`${12}`t`${13}`t`${14}`t`${15}`t`${16}`t`${17}`t`${18}`t`${19}`t`${20}`t`${21}`t`${22}`t`${23}`t`${24}`t`${25}`t`${26}`t`${27}`t`${28}`t`${29}`t`${30}`t`${31}`t`${32}`t`${33}"

Get-Content $infile -ReadCount $batch |

    foreach {

        $_ -replace $match_regex, $replace_regex | Out-File $outfile -Append

    }

Любая помощь, которую вы можете оказать, приветствуется!

1 Ответ

1 голос
/ 09 ноября 2019

Оператор switch с параметром -File - это самый быстрый способ обработки больших файлов в PowerShell [1] :

& { 
  switch -File $infile -Regex  {
    $match_regex {
       # Join the what all the capture groups matched, trimmed, with a tab char.
       $Matches[1..($Matches.Count-1)].Trim() -join "`t"
    }
  }
} | Out-File $outFile # or: Set-Content $outFile (beware encoding issues)

С выводом текста,Out-File и Set-Content могут использоваться взаимозаменяемо, но не в том, что в Windows PowerShell они по умолчанию используют разные кодировки символов (UTF-16LE и Ansi);используйте -Encoding по мере необходимости;PowerShell Core постоянно использует UTF-8 без спецификации.

Примечание:

  • Чтобы пропустить строку заголовка или захватите его отдельно, либо предоставьте для него отдельное регулярное выражение, либо, если заголовок также соответствует регулярному выражению строки данных, инициализируйте переменную индекса строки перед оператором switch (например, $i = 0) и проверьте и увеличьте егопеременная в блоке сценария обработки (например, if ($i++ -eq 0) { ... }).

  • .Trim() неявно вызывается для каждой строки в массиве , возвращаемой $Matches[1..($Matches.Count-1)];эта функция называется перечисление члена

  • Причина, по которой оператор switch заключен в & { ... } ( блок сценария ({ ... }), вызываемый оператором вызова (&) ), заключается в том, что составные операторы, такие как switch / while, foreach (...), ... не являются напрямую поддерживается в качестве входных данных конвейера - см. этот выпуск GitHub .


Что касается того, что вы пытались :

Как указывает iRon , вы не должны использовать $Input в качестве пользовательской переменной - это автоматическая переменная , управляемая PowerShellи, фактически, все, что вы назначаете ему, тихо отбрасывается .

Как AdminOfThings указывает:

  • $element = $_.trim() не работает, потому что вы находитесь внутри foreach цикла , а не в конвейере с командлетом ForEach-Object (хотя последний также связан сforeach; только с ForEach-Object будет установлено $_ вurrent входной объект.

  • Нет необходимости в пользовательской функции только для соединения элементов массива с разделителем;оператор -join делает это напрямую, как показано выше.

Lee_Daily показывает, как использовать -join напрямую с массивом $Matches, как использовалось выше.

Несколько сторон:

Join-Str($matches)

Вместо этого следует использовать Join-Str $matches:

В PowerShell функции вызываются как команды оболочки - foo arg1 arg2 - не как методы C # - foo(arg1, arg2);см. Get-Help about_Parsing.
Если вы используете , для разделения аргументов, вы создадите массив , который функция будет рассматривать как единственный аргумент.
Чтобы предотвратить случайное использование синтаксиса метода, используйте Set-StrictMode -Version 2 или выше, но обратите внимание на его другие эффекты.

| Out-Null

Почти всегда более быстрый метод подавления выходного сигнала заключается в использовании $null = ....


[1] Mark (OP) сообщает о значительном ускорении по сравнению с Get-Content+ ForEach-Object подход в вопросе (решение switch занимает 7,7 минут для файла объемом 4 ГБ).
Хотя решение switch, скорее всего, достаточно быстрое в большинстве сценариев, этот ответ показываетрешение, которое может быть быстрее для большого числа итераций; этот ответ противопоставляет его решению switch и показывает тесты с переменным числом итераций.
Помимо этого, скомпилированное решение, написанное, скажем, на C #, является единственным способомдля дальнейшего улучшения производительности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...