Як обробити файл у PowerShell по черзі як потік


87

Я працюю з декількома гігабайтними текстовими файлами і хочу виконати певну обробку потоків над ними за допомогою PowerShell. Це прості речі, просто проаналізуйте кожен рядок і витягніть деякі дані, а потім збережіть їх у базі даних.

На жаль, get-content | %{ whatever($_) }схоже, він зберігає в пам'яті весь набір рядків на цьому етапі конвеєра. Це також напрочуд повільно, адже потрібно дуже багато часу, щоб насправді прочитати все.

Отже, моє запитання складається з двох частин:

  1. Як я можу змусити його обробляти потік за рядком і не зберігати все, що буферизується в пам'яті? Я хотів би уникати використання декількох концертів оперативної пам'яті для цієї мети.
  2. Як я можу змусити його працювати швидше? Ітерація PowerShell над, get-contentздається, у 100 разів повільніша, ніж сценарію C #.

Я сподіваюся, що я роблю тут щось німе, наприклад, пропускаючи -LineBufferSizeпараметр чи щось ...


9
Для прискорення get-contentвстановіть -ReadCount на 512. Зверніть увагу, що на даний момент $ _ у Foreach буде масивом рядків.
Кіт Хілл,

1
Тим не менше, я б погодився з пропозицією Романа використовувати програму для читання .NET - набагато швидше.
Кіт Хілл,

З цікавості, що станеться, якщо мене піклує не швидкість, а лише пам’ять? Швидше за все, я подамся на пропозицію читача .NET, але мені також цікаво знати, як уникнути буферизації всієї труби в пам'яті.
Скобі

7
Щоб мінімізувати буферизацію, уникайте присвоєння результату Get-Contentзмінної, оскільки це завантажить весь файл в пам’ять. За замовчуванням у піпейнлі Get-Contentобробляє файл по одному рядку за раз. Поки ви не накопичуєте результати або не використовуєте командлет, який накопичується внутрішньо (наприклад, Sort-Object та Group-Object), тоді звернення до пам'яті не повинно бути дуже поганим. Foreach-Object (%) - це безпечний спосіб обробки кожного рядка, по одному.
Кіт Хілл,

2
@dwarfsoft, що не має жодного сенсу. Блок -End запускається лише один раз після закінчення всієї обробки. Ви можете бачити, що якщо ви спробуєте використати, get-content | % -End { }то він скаржиться, оскільки ви не надали блок процесу. Отже, він не може використовувати -End за замовчуванням, він повинен використовувати -Process за замовчуванням. І спробуйте 1..5 | % -process { } -end { 'q' }побачити, що кінцевий блок трапляється лише один раз, звичайний gc | % { $_ }не спрацював би, якщо блок сценаріїв за замовчуванням був -End ...
TessellatingHeckler

Відповіді:


92

Якщо ви дійсно збираєтеся працювати з текстовими файлами з кількістю гігабайт, тоді не використовуйте PowerShell. Навіть якщо ви знайдете спосіб його прочитати, швидша обробка величезної кількості рядків у будь-якому випадку буде повільною в PowerShell, і ви не можете цього уникнути. Навіть прості цикли дорогі, скажімо, за 10 мільйонів ітерацій (цілком реально у вашому випадку) ми маємо:

# "empty" loop: takes 10 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) {} }

# "simple" job, just output: takes 20 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) { $i } }

# "more real job": 107 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) { $i.ToString() -match '1' } }

ОНОВЛЕННЯ: Якщо ви все ще не боїтеся, спробуйте скористатися програмою зчитування .NET:

$reader = [System.IO.File]::OpenText("my.log")
try {
    for() {
        $line = $reader.ReadLine()
        if ($line -eq $null) { break }
        # process the line
        $line
    }
}
finally {
    $reader.Close()
}

ОНОВЛЕННЯ 2

Є коментарі щодо можливо кращого / коротшого коду. В оригінальному коді немає нічого поганого, forі це не псевдокод. Але найкоротший (найкоротший?) Варіант циклу читання

$reader = [System.IO.File]::OpenText("my.log")
while($null -ne ($line = $reader.ReadLine())) {
    $line
}

3
FYI, компіляція сценаріїв у PowerShell V3 трохи покращує ситуацію. Цикл "реальної роботи" перейшов зі 117 секунд на V2 до 62 секунд на V3, набраних на консолі. Коли я вкладаю цикл у сценарій і вимірюю виконання сценарію на V3, він падає до 34 секунд.
Кіт Хілл

Я помістив усі три тести в сценарій і отримав такі результати: V3 Beta: 20/27/83 секунди; V2: 14/21/101. Схоже, у моєму експерименті V3 швидший у тесті 3, але він досить повільніший у перші два. Ну, це бета-версія, сподіваємось, продуктивність буде покращена в RTM.
Роман Кузьмін

чому люди наполягають на тому, щоб використовувати такий розрив у циклі. Чому б не використовувати цикл, який цього не вимагає, і читається краще, наприклад, замінюючи цикл for наdo { $line = $reader.ReadLine(); $line } while ($line -neq $null)
BeowulfNode42,

1
ой, це має бути -ne для не рівного. Цей конкретний цикл do.. While має проблему з обробкою нуля в кінці файлу (у цьому випадку виводу). Щоб обійти це теж можнаfor ( $line = $reader.ReadLine(); $line -ne $null; $line = $reader.ReadLine() ) { $line }
BeowulfNode42,

4
@ BeowulfNode42, ми можемо зробити це ще коротше: while($null -ne ($line = $read.ReadLine())) {$line}. Але тема насправді не про такі речі.
Роман Кузьмін

51

System.IO.File.ReadLines()ідеально підходить для цього сценарію. Він повертає всі рядки файлу, але дозволяє негайно розпочати ітерацію по рядках, що означає, що йому не потрібно зберігати весь вміст у пам'яті.

Потрібна .NET 4.0 або новіша версія.

foreach ($line in [System.IO.File]::ReadLines($filename)) {
    # do something with $line
}

http://msdn.microsoft.com/en-us/library/dd383503.aspx


6
Потрібна примітка: .NET Framework - Підтримується в: 4.5, 4. Таким чином, це може не працювати в V2 або V1 на деяких машинах.
Роман Кузьмін,

Це дало мені помилку System.IO.File, але код вище, написаний Романом, спрацював для мене
Каньйон Колоба

Це було саме те, що мені потрібно, і легко було потрапити безпосередньо в існуючий скрипт PowerShell.
user1751825

5

Якщо ви хочете використовувати пряму PowerShell, ознайомтеся з наведеним нижче кодом.

$content = Get-Content C:\Users\You\Documents\test.txt
foreach ($line in $content)
{
    Write-Host $line
}

16
Це те, від чого хотів позбутися OP, оскільки Get-Contentвін дуже повільний щодо великих файлів.
Роман Кузьмін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.