Построчное чтение файла в PowerShell


103

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

Я знаю эквивалент Bash:

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

Ответы:


180

Не так много документации по циклам 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, который никогда не упоминался как проблема в OP.
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. Также существует псевдоним по умолчанию foreachfor ForEach-Object, но он используется только при наличии конвейера. См. Подробное объяснение Get-Help about_Foreachили щелкните ссылку в моем предыдущем комментарии, который ведет ко всей статье Microsoft The Scripting Guys о различиях между оператором и командой.
Bacon Bits

4
@BaconBits blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/… Узнал кое-что новое. Спасибо. Я предположил, что они были Get-Alias foreachForeach-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.