Baca file baris demi baris di PowerShell

103

Saya ingin membaca file baris demi baris di PowerShell. Secara khusus, saya ingin mengulang melalui file, menyimpan setiap baris dalam variabel dalam loop, dan melakukan beberapa pemrosesan pada baris tersebut.

Saya tahu padanan Bash:

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

Tidak banyak dokumentasi tentang loop PowerShell.

Kingamere
sumber
Jawaban yang dipilih dari Mathias bukanlah solusi yang bagus. Get-Contentmemuat seluruh file ke dalam memori sekaligus, yang akan gagal atau macet pada file besar.
Kolob Canyon
1
@Kolobanyon itu sama sekali tidak benar. Secara default, Get-Content memuat setiap baris sebagai satu objek di pipeline. Jika Anda menyalurkan ke fungsi yang tidak menentukan processblok, dan mengeluarkan objek lain per baris ke dalam pipa, maka fungsi itu adalah masalahnya. Masalah apa pun dengan memuat konten lengkap ke dalam memori bukanlah kesalahannya Get-Content.
The Fish
@TheFish foreach($line in Get-Content .\file.txt)Ini akan memuat seluruh file ke dalam memori sebelum mulai iterasi. Jika Anda tidak percaya, dapatkan file log 1GB dan coba.
Kolob Canyon
2
@Kolobanyon Bukan itu yang kamu katakan. Anda mengatakan bahwa Get-Content memuat semuanya ke dalam memori yang tidak benar. Contoh Anda yang berubah dari foreach akan, ya; foreach tidak sadar akan pipa. Get-Content .\file.txt | ForEach-Object -Process {}sadar pipeline, dan tidak akan memuat seluruh file ke dalam memori. Secara default Get-Content akan melewatkan satu baris pada satu waktu melalui pipeline.
The Fish

Jawaban:

180

Tidak banyak dokumentasi tentang loop PowerShell.

Dokumentasi pada loop dalam PowerShell berlimpah, dan Anda mungkin ingin memeriksa bantuan topik berikut: about_For, about_ForEach, about_Do, about_While.

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

Solusi PowerShell idiomatik lain untuk masalah Anda adalah menyalurkan baris file teks ke ForEach-Objectcmdlet :

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

Alih-alih mencocokkan ekspresi reguler di dalam loop, Anda dapat menyalurkan garis Where-Objectuntuk memfilter hanya yang Anda minati:

Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object {
    # Work here
}
Mathias R. Jessen
sumber
Tautan tidak rusak, tetapi sekarang dialihkan ke docs.microsoft.com.
Peter Mortensen
@KolobCanyon yang tidak pernah disebutkan sebagai masalah di OP.
The Fish
53

Get-Contentberkinerja buruk; itu mencoba untuk membaca file ke dalam memori sekaligus.

Pembaca file C # (.NET) membaca setiap baris satu per satu

Performace Terbaik

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

Atau kurang berkinerja

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

The foreachpernyataan kemungkinan akan sedikit lebih cepat daripada ForEach-Object(lihat komentar di bawah ini untuk informasi lebih lanjut).

Ngarai Kolob
sumber
5
Saya mungkin akan menggunakan [System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object { ... }. The foreachpernyataan akan memuat seluruh koleksi ke objek . ForEach-Objectmenggunakan pipa untuk melakukan streaming. Sekarang foreachpernyataan itu kemungkinan akan sedikit lebih cepat daripada ForEach-Objectperintah, tetapi itu karena memuat semuanya ke memori biasanya lebih cepat. Get-Contentmasih buruk.
Bacon Bits
@BaconBits foreach()adalah alias dariForeach-Object
Kolob Canyon
16
Itu adalah kesalahpahaman yang sangat umum. foreachadalah pernyataan, seperti if, for, atau while. ForEach-Objectadalah perintah, seperti Get-ChildItem. Ada juga alias default foreachuntuk ForEach-Object, tetapi hanya digunakan jika ada pipeline. Lihat penjelasan panjangnya di Get-Help about_Foreach, atau klik tautan di komentar saya sebelumnya yang mengarah ke seluruh artikel oleh Microsoft's The Scripting Guys tentang perbedaan antara pernyataan dan perintah.
Bacon Bits
4
@BaconBits blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/… Mempelajari sesuatu yang baru. Terima kasih. Saya berasumsi keduanya sama karena Get-Alias foreach=> Foreach-Object, tetapi Anda benar, ada perbedaan
Kolob Canyon
2
Itu akan berhasil, tetapi Anda ingin mengubahnya $lineke $_dalam blok skrip loop.
Bacon Bits
1

Sakelar mahakuasa bekerja dengan baik di sini:

'one
two
three' > file

$regex = '^t'

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

Keluaran:

line is two
line is three
js2010
sumber