Zeile für Zeile eine Datei in Go lesen

332

Ich kann keine file.ReadLineFunktion in Go finden. Ich kann herausfinden, wie man schnell einen schreibt, aber ich frage mich nur, ob ich hier etwas übersehen habe. Wie liest man eine Datei Zeile für Zeile?

g06lin
quelle
7
Ab Go1.1 ist bufio.Scanner der beste Weg, dies zu tun.
Malcolm

Antworten:

133

HINWEIS: Die akzeptierte Antwort war in früheren Versionen von Go korrekt. Die Antwort mit der höchsten Stimmenzahl enthält den neueren idiomatischen Weg, um dies zu erreichen.

Es gibt die Funktion ReadLine im Paket bufio.

Bitte beachten Sie, dass die Funktion eine unvollständige Zeile zurückgibt, wenn die Zeile nicht in den Lesepuffer passt. Wenn Sie immer eine ganze Zeile in Ihrem Programm durch einen einzelnen Aufruf einer Funktion lesen möchten, müssen Sie die ReadLineFunktion in Ihre eigene Funktion kapseln, die ReadLinein einer for-Schleife aufruft .

bufio.ReadString('\n')ist nicht vollständig äquivalent zu, ReadLineda ReadStringder Fall nicht behandelt werden kann, wenn die letzte Zeile einer Datei nicht mit dem Zeilenumbruchzeichen endet.

Samuel Hawksby-Robinson
quelle
37
Aus den Dokumenten: "ReadLine ist ein Grundelement zum Lesen von Zeilen auf niedriger Ebene. Die meisten Anrufer sollten stattdessen ReadBytes ('\ n') oder ReadString ('\ n') oder einen Scanner verwenden."
Mdwhatcott
12
@mdwhatcott warum ist es wichtig, dass es ein "Low-Level-Zeilenlese-Primitiv" ist? Wie kommt man zu dem Schluss, dass "die meisten Anrufer stattdessen ReadBytes ('\ n') oder ReadString ('\ n') oder einen Scanner verwenden sollten"?
Charlie Parker
12
@CharlieParker - Nicht sicher, nur die Dokumente zitieren, um Kontext hinzuzufügen.
Mdwhatcott
11
Aus denselben Dokumenten. "Wenn ReadString vor dem Auffinden eines Trennzeichens auf einen Fehler stößt, werden die vor dem Fehler gelesenen Daten und der Fehler selbst (häufig io.EOF) zurückgegeben." Sie können also einfach nach io.EOF-Fehlern suchen und wissen, dass Sie fertig sind.
eduncan911
1
Beachten Sie, dass ein Lese- oder Schreibvorgang aufgrund eines unterbrochenen Systemaufrufs fehlschlagen kann, was dazu führt, dass weniger als die erwartete Anzahl von Bytes gelesen oder geschrieben wird.
Justin Swanhart
597

In Go 1.1 und höher ist der einfachste Weg, dies zu tun, mit a bufio.Scanner. Hier ist ein einfaches Beispiel, das Zeilen aus einer Datei liest:

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    file, err := os.Open("/path/to/file.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

Dies ist die sauberste Art, ReaderZeile für Zeile zu lesen .

Es gibt eine Einschränkung: Der Scanner kann mit Zeilen, die länger als 65536 Zeichen sind, nicht gut umgehen. Wenn das ein Problem für Sie ist, sollten Sie wahrscheinlich Ihr eigenes darüber rollen Reader.Read().

Stefan Arentz
quelle
40
Und da das OP darum gebeten hat, eine Datei zu scannen, wäre es trivial, zuerst das Dateihandle zu scannen file, _ := os.Open("/path/to/file.csv"):scanner := bufio.NewScanner(file)
Evan Plumlee
14
Vergiss es nicht defer file.Close().
Kiril
13
Das Problem ist, dass Scanner.Scan () auf eine Puffergröße von 4096 [] Byte pro Zeile begrenzt ist. Sie erhalten eine bufio.ErrTooLongFehlermeldung, bufio.Scanner: token too longwenn die Leitung zu lang ist. In diesem Fall müssen Sie bufio.ReaderLine () oder ReadString () verwenden.
eduncan911
5
Nur meine $ 0.02 - das ist die richtigste Antwort auf der Seite :)
Sethvargo
5
Sie können den Scanner so konfigurieren, dass er mit der Buffer () -Methode noch längere Zeilen verarbeitet: golang.org/pkg/bufio/#Scanner.Buffer
Alex Robinson
77

Verwenden:

  • reader.ReadString('\n')
    • Wenn Sie nichts dagegen haben, dass die Leitung sehr lang sein kann (dh viel RAM verwenden). Es behält die \nam Ende der Zeichenfolge zurückgegebene.
  • reader.ReadLine()
    • Wenn Sie den RAM-Verbrauch begrenzen möchten und sich nicht um die zusätzliche Arbeit kümmern müssen, wenn die Zeile größer als die Puffergröße des Lesers ist.

Ich habe die verschiedenen Lösungsvorschläge getestet, indem ich ein Programm geschrieben habe, um die Szenarien zu testen, die in anderen Antworten als Probleme identifiziert werden:

  • Eine Datei mit einer 4-MB-Zeile.
  • Eine Datei, die nicht mit einem Zeilenumbruch endet.

Ich habe das gefunden:

  • Die ScannerLösung verarbeitet keine langen Schlangen.
  • Die ReadLineLösung ist komplex zu implementieren.
  • Die ReadStringLösung ist die einfachste und funktioniert für lange Schlangen.

Hier ist Code, der jede Lösung demonstriert und über Folgendes ausgeführt werden kann go run main.go:

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "os"
)

func readFileWithReadString(fn string) (err error) {
    fmt.Println("readFileWithReadString")

    file, err := os.Open(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    // Start reading from the file with a reader.
    reader := bufio.NewReader(file)

    var line string
    for {
        line, err = reader.ReadString('\n')

        fmt.Printf(" > Read %d characters\n", len(line))

        // Process the line here.
        fmt.Println(" > > " + limitLength(line, 50))

        if err != nil {
            break
        }
    }

    if err != io.EOF {
        fmt.Printf(" > Failed!: %v\n", err)
    }

    return
}

func readFileWithScanner(fn string) (err error) {
    fmt.Println("readFileWithScanner - this will fail!")

    // Don't use this, it doesn't work with long lines...

    file, err := os.Open(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    // Start reading from the file using a scanner.
    scanner := bufio.NewScanner(file)

    for scanner.Scan() {
        line := scanner.Text()

        fmt.Printf(" > Read %d characters\n", len(line))

        // Process the line here.
        fmt.Println(" > > " + limitLength(line, 50))
    }

    if scanner.Err() != nil {
        fmt.Printf(" > Failed!: %v\n", scanner.Err())
    }

    return
}

func readFileWithReadLine(fn string) (err error) {
    fmt.Println("readFileWithReadLine")

    file, err := os.Open(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    // Start reading from the file with a reader.
    reader := bufio.NewReader(file)

    for {
        var buffer bytes.Buffer

        var l []byte
        var isPrefix bool
        for {
            l, isPrefix, err = reader.ReadLine()
            buffer.Write(l)

            // If we've reached the end of the line, stop reading.
            if !isPrefix {
                break
            }

            // If we're just at the EOF, break
            if err != nil {
                break
            }
        }

        if err == io.EOF {
            break
        }

        line := buffer.String()

        fmt.Printf(" > Read %d characters\n", len(line))

        // Process the line here.
        fmt.Println(" > > " + limitLength(line, 50))
    }

    if err != io.EOF {
        fmt.Printf(" > Failed!: %v\n", err)
    }

    return
}

func main() {
    testLongLines()
    testLinesThatDoNotFinishWithALinebreak()
}

func testLongLines() {
    fmt.Println("Long lines")
    fmt.Println()

    createFileWithLongLine("longline.txt")
    readFileWithReadString("longline.txt")
    fmt.Println()
    readFileWithScanner("longline.txt")
    fmt.Println()
    readFileWithReadLine("longline.txt")
    fmt.Println()
}

func testLinesThatDoNotFinishWithALinebreak() {
    fmt.Println("No linebreak")
    fmt.Println()

    createFileThatDoesNotEndWithALineBreak("nolinebreak.txt")
    readFileWithReadString("nolinebreak.txt")
    fmt.Println()
    readFileWithScanner("nolinebreak.txt")
    fmt.Println()
    readFileWithReadLine("nolinebreak.txt")
    fmt.Println()
}

func createFileThatDoesNotEndWithALineBreak(fn string) (err error) {
    file, err := os.Create(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    w := bufio.NewWriter(file)
    w.WriteString("Does not end with linebreak.")
    w.Flush()

    return
}

func createFileWithLongLine(fn string) (err error) {
    file, err := os.Create(fn)
    defer file.Close()

    if err != nil {
        return err
    }

    w := bufio.NewWriter(file)

    fs := 1024 * 1024 * 4 // 4MB

    // Create a 4MB long line consisting of the letter a.
    for i := 0; i < fs; i++ {
        w.WriteRune('a')
    }

    // Terminate the line with a break.
    w.WriteRune('\n')

    // Put in a second line, which doesn't have a linebreak.
    w.WriteString("Second line.")

    w.Flush()

    return
}

func limitLength(s string, length int) string {
    if len(s) < length {
        return s
    }

    return s[:length]
}

Ich habe getestet an:

  • go version go1.7 windows / amd64
  • go version go1.6.3 linux / amd64
  • go version go1.7.4 darwin / amd64

Das Testprogramm gibt aus:

Long lines

readFileWithReadString
 > Read 4194305 characters
 > > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 > Read 12 characters
 > > Second line.

readFileWithScanner - this will fail!
 > Failed!: bufio.Scanner: token too long

readFileWithReadLine
 > Read 4194304 characters
 > > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 > Read 12 characters
 > > Second line.

No linebreak

readFileWithReadString
 > Read 28 characters
 > > Does not end with linebreak.

readFileWithScanner - this will fail!
 > Read 28 characters
 > > Does not end with linebreak.

readFileWithReadLine
 > Read 28 characters
 > > Does not end with linebreak.
Ah
quelle
9
Das defer file.Close()sollte nach der Fehlerprüfung sein; Andernfalls gerät es bei einem Fehler in Panik.
mlg
Die Scannerlösung verarbeitet die langen Schlangen, wenn Sie sie so konfigurieren. Siehe: golang.org/pkg/bufio/#Scanner.Buffer
Inanc Gumus
Sie sollten den Fehler ordnungsgemäß überprüfen, wie in den Dokumenten gezeigt: play.golang.org/p/5CCPzVTSj6 dh dh wenn err == io.EOF {break} else {return err}
Chuque
53

EDIT: Ab go1.1 besteht die idiomatische Lösung darin, bufio.Scanner zu verwenden

Ich habe eine Möglichkeit geschrieben, jede Zeile einfach aus einer Datei zu lesen. Die Funktion Readln (* bufio.Reader) gibt eine Zeile (sans \ n) von der zugrunde liegenden Struktur bufio.Reader zurück.

// Readln returns a single line (without the ending \n)
// from the input buffered reader.
// An error is returned iff there is an error with the
// buffered reader.
func Readln(r *bufio.Reader) (string, error) {
  var (isPrefix bool = true
       err error = nil
       line, ln []byte
      )
  for isPrefix && err == nil {
      line, isPrefix, err = r.ReadLine()
      ln = append(ln, line...)
  }
  return string(ln),err
}

Mit Readln können Sie jede Zeile aus einer Datei lesen. Der folgende Code liest jede Zeile in einer Datei und gibt jede Zeile an stdout aus.

f, err := os.Open(fi)
if err != nil {
    fmt.Printf("error opening file: %v\n",err)
    os.Exit(1)
}
r := bufio.NewReader(f)
s, e := Readln(r)
for e == nil {
    fmt.Println(s)
    s,e = Readln(r)
}

Prost!

Malcolm
quelle
14
Ich habe diese Antwort geschrieben, bevor Go 1.1 herauskam. Go 1.1 hat ein Scanner-Paket in der stdlib. das bietet die gleiche Funktionalität wie meine Antwort. Ich würde empfehlen, Scanner anstelle meiner Antwort zu verwenden, da sich Scanner in der stdlib befindet. Viel Spaß beim Hacken! :-)
Malcolm
30

Es gibt zwei gängige Methoden, um Dateien Zeile für Zeile zu lesen.

  1. Verwenden Sie bufio.Scanner
  2. Verwenden Sie ReadString / ReadBytes / ... in bufio.Reader

In meinem Testfall ist bufio.Scanner (verwendete Zeit: 0,395491384s) mit ~ 250 MB, ~ 2.500.000 Zeilen schneller als bufio.Reader.ReadString (time_used: 0,446867622s).

Quellcode: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line

Datei lesen mit bufio.Scanner,

func scanFile() {
    f, err := os.OpenFile(logfile, os.O_RDONLY, os.ModePerm)
    if err != nil {
        log.Fatalf("open file error: %v", err)
        return
    }
    defer f.Close()

    sc := bufio.NewScanner(f)
    for sc.Scan() {
        _ = sc.Text()  // GET the line string
    }
    if err := sc.Err(); err != nil {
        log.Fatalf("scan file error: %v", err)
        return
    }
}

Datei lesen use bufio.Reader,

func readFileLines() {
    f, err := os.OpenFile(logfile, os.O_RDONLY, os.ModePerm)
    if err != nil {
        log.Fatalf("open file error: %v", err)
        return
    }
    defer f.Close()

    rd := bufio.NewReader(f)
    for {
        line, err := rd.ReadString('\n')
        if err != nil {
            if err == io.EOF {
                break
            }

            log.Fatalf("read file line error: %v", err)
            return
        }
        _ = line  // GET the line string
    }
}
zouying
quelle
Beachten Sie, dass in diesem bufio.ReaderBeispiel die letzte Zeile in einer Datei nicht gelesen wird, wenn sie nicht mit einer neuen Zeile endet. ReadStringgibt sowohl die letzte Zeile als auch io.EOFin diesem Fall zurück.
Konrad
18

Beispiel aus diesem Kern

func readLine(path string) {
  inFile, err := os.Open(path)
  if err != nil {
     fmt.Println(err.Error() + `: ` + path)
     return
  }
  defer inFile.Close()

  scanner := bufio.NewScanner(inFile)
  for scanner.Scan() {
    fmt.Println(scanner.Text()) // the line
  }
}

Dies führt jedoch zu einem Fehler, wenn eine Zeile größer als der Puffer des Scanners ist.

Wenn das passiert ist reader := bufio.NewReader(inFile), benutze ich create und concat meinen eigenen Puffer entweder mit ch, err := reader.ReadByte()oderlen, err := reader.Read(myBuffer)

Eine andere Art, die ich benutze (os.Stdin durch Datei wie oben ersetzen), ist diese, wenn lange Zeilen lang sind (isPrefix) und leere Zeilen ignoriert:


func readLines() []string {
  r := bufio.NewReader(os.Stdin)
  bytes := []byte{}
  lines := []string{}
  for {
    line, isPrefix, err := r.ReadLine()
    if err != nil {
      break
    }
    bytes = append(bytes, line...)
    if !isPrefix {
      str := strings.TrimSpace(string(bytes))
      if len(str) > 0 {
        lines = append(lines, str)
        bytes = []byte{}
      }
    }
  }
  if len(bytes) > 0 {
    lines = append(lines, string(bytes))
  }
  return lines
}
Kokizzu
quelle
Möchtest -1du erklären warum ?
Kokizzu
Ich denke schon, diese Lösung ist ein bisschen zu kompliziert, nicht wahr?
Decebal
10

Sie können ReadString auch mit \ n als Trennzeichen verwenden:

  f, err := os.Open(filename)
  if err != nil {
    fmt.Println("error opening file ", err)
    os.Exit(1)
  }
  defer f.Close()
  r := bufio.NewReader(f)
  for {
    path, err := r.ReadString(10) // 0x0A separator = newline
    if err == io.EOF {
      // do something here
      break
    } else if err != nil {
      return err // if you return error
    }
  }
lzap
quelle
3
// strip '\n' or read until EOF, return error if read error  
func readline(reader io.Reader) (line []byte, err error) {   
    line = make([]byte, 0, 100)                              
    for {                                                    
        b := make([]byte, 1)                                 
        n, er := reader.Read(b)                              
        if n > 0 {                                           
            c := b[0]                                        
            if c == '\n' { // end of line                    
                break                                        
            }                                                
            line = append(line, c)                           
        }                                                    
        if er != nil {                                       
            err = er                                         
            return                                           
        }                                                    
    }                                                        
    return                                                   
}                                    
Cyber
quelle
1

Im folgenden Code lese ich die Interessen aus der CLI, bis der Benutzer die Eingabetaste drückt und ich Readline verwende:

interests := make([]string, 1)
r := bufio.NewReader(os.Stdin)
for true {
    fmt.Print("Give me an interest:")
    t, _, _ := r.ReadLine()
    interests = append(interests, string(t))
    if len(t) == 0 {
        break;
    }
}
fmt.Println(interests)
zuzuleinen
quelle
0

Ich mag die Lzap-Lösung, ich bin neu in Go, ich würde gerne nach lzap fragen, aber ich konnte es nicht tun. Ich habe noch keine 50 Punkte. Ich ändere ein wenig Ihre Lösung und vervollständige den Code ...

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    f, err := os.Open("archiveName")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    defer f.Close()
    r := bufio.NewReader(f)
    line, err := r.ReadString(10)    // line defined once 
    for err != io.EOF {
        fmt.Print(line)              // or any stuff
        line, err = r.ReadString(10) //  line was defined before
    }
}

Ich bin mir nicht sicher, warum ich 'err' erneut testen muss, aber wir können es trotzdem tun. Die Hauptfrage ist jedoch, warum Go keinen Fehler mit dem Satz => line, err: = r.ReadString (10) innerhalb der Schleife erzeugt. Sie wird bei jeder Ausführung der Schleife immer wieder definiert. Ich vermeide diese Situation mit meiner Änderung, irgendeinen Kommentar? Ich habe die Bedingung EOF im 'für' ähnlich wie bei einer Weile eingestellt. Vielen Dank

Jose.mg
quelle
0
import (
     "bufio"
     "os"
)

var (
    reader = bufio.NewReader(os.Stdin)
)

func ReadFromStdin() string{
    result, _ := reader.ReadString('\n')
    witl := result[:len(result)-1]
    return witl
}

Hier ist ein Beispiel mit einer Funktion ReadFromStdin(), wie sie ist, fmt.Scan(&name)aber alle Zeichenfolgen mit Leerzeichen wie "Hallo, mein Name ist ..."

var name string = ReadFromStdin()

println(name)
0DAYanc
quelle