Як обробити файл у 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.