Читайте файл за рядком у PowerShell


100

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

Я знаю еквівалент Баша:

while read line do
    if [[ $line =~ $regex ]]; then
          # work here
    fi
done < file.txt

Небагато документації щодо циклів PowerShell.


Обрана відповідь від Матіаса не є чудовим рішенням. Get-Contentзавантажує весь файл відразу в пам’ять, що не вдасться або зависне на великих файлах.
Каньйон

1
@KolobCanyon, що абсолютно не відповідає дійсності. За замовчуванням Get-Content завантажує кожен рядок як один об’єкт у конвеєрі. Якщо ви трубитесь до функції, яка не визначає processблок, і випльовує інший об'єкт на рядок у трубопровід, то проблема полягає в цій функції. Будь-які проблеми із завантаженням повного вмісту в пам'ять не є виною Get-Content.
The Fish

@TheFish foreach($line in Get-Content .\file.txt)Він завантажить весь файл у пам'ять, перш ніж він почне ітерацію. Якщо ви мені не вірите, прийміть файл журналу розміром 1 ГБ і спробуйте.
Каньйон

2
@KolobCanyon Це не те, що ти сказав. Ви сказали, що Get-Content завантажує все це в пам'ять, що не відповідає дійсності. Так би змінився ваш приклад foreach; foreach не знає про конвеєр. Get-Content .\file.txt | ForEach-Object -Process {}знає конвеєр і не завантажує весь файл у пам’ять. За замовчуванням Get-Content проходить по одному рядку за конвеєром.
The Fish

Відповіді:


176

Небагато документації щодо циклів PowerShell.

Документація на петлях в PowerShell багато, і ви можете перевірити наступні розділи довідки: about_For, about_ForEach, about_Do, about_While.

foreach($line in Get-Content .\file.txt) {
    if($line -match $regex){
        # Work here
    }
}

Іншим ідіоматичним рішенням вашої проблеми PowerShell є передача рядків текстового файлу до ForEach-Objectкомандлета :

Get-Content .\file.txt | ForEach-Object {
    if($_ -match $regex){
        # Work here
    }
}

Замість відповідності регулярних виразів всередині циклу ви можете прокласти рядки, Where-Objectщоб відфільтрувати лише тих, хто вас цікавить:

Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object {
    # Work here
}

Посилання не порушені, але тепер вони переспрямовують на docs.microsoft.com.
Пітер Мортенсен,

@KolobCanyon, який ніколи не згадувався як проблема в ОП.
The Fish,

53

Get-Contentмає погані показники; він намагається зразу прочитати файл у пам'ять.

Зчитувач файлів C # (.NET) зчитує кожен рядок по одному

Найкраще виконання

foreach($line in [System.IO.File]::ReadLines("C:\path\to\file.txt"))
{
       $line
}

Або трохи менш продуктивні

[System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object {
       $_
}

foreachЗаява, ймовірно , буде трохи швидше , ніж ForEach-Object(див коментарі нижче для отримання додаткової інформації).


5
Я б, мабуть, використав [System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object { ... }. foreachЗаява буде завантажувати всю колекцію об'єкта . ForEach-Objectвикористовує конвеєр для потокової передачі. Тепер foreachоператор, швидше за все, буде трохи швидшим за ForEach-Objectкоманду, але це тому, що завантаження всього в пам'ять зазвичай відбувається швидше. Get-Contentвсе ж таки страшно.
Bacon Bits

@BaconBits foreach()- це псевдонімForeach-Object
Каньйон Колоба

16
Це дуже поширена помилкова думка. foreachце твердження, як if, forабо while. ForEach-Objectце команда, наприклад Get-ChildItem. Також існує псевдонім за замовчуванням foreachдля ForEach-Object, але він використовується лише тоді, коли існує конвеєр. Подивіться довге пояснення Get-Help about_Foreachабо натисніть на посилання в моєму попередньому коментарі, яке переходить до цілої статті Microsoft's The Scripting Guys про відмінності між твердженням і командою.
Bacon Bits

4
@BaconBits blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/… Дізнався щось нове. Дякую. Я припустив, що вони однакові, тому що Get-Alias foreach=> Foreach-Object, але ти маєш рацію, є відмінності
Каньйон Колоба

2
Це буде працювати, але ви захочете змінити $lineйого $_на блок сценарію циклу.
Bacon Bits

1

Тут добре працює всемогутній перемикач:

'one
two
three' > file

$regex = '^t'

switch -regex -file file { 
  $regex { "line is $_" } 
}

Вихід:

line is two
line is three
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.