Leer un archivo línea por línea en Ir

335

No puedo encontrar la file.ReadLinefunción en Go. Puedo descubrir cómo escribir uno rápidamente, pero me pregunto si estoy pasando por alto algo aquí. ¿Cómo se lee un archivo línea por línea?

g06lin
fuente
77
A partir de Go1.1, bufio.Scanner es la mejor manera de hacerlo.
Malcolm

Respuestas:

133

NOTA: La respuesta aceptada fue correcta en las primeras versiones de Go. Ver la respuesta más votada contiene la forma idiomática más reciente para lograr esto.

Hay una función ReadLine en el paquete bufio.

Tenga en cuenta que si la línea no cabe en el búfer de lectura, la función devolverá una línea incompleta. Si desea leer siempre una línea completa en su programa mediante una sola llamada a una función, deberá encapsular la ReadLinefunción en su propia función que llama ReadLinea un bucle for.

bufio.ReadString('\n')no es totalmente equivalente a ReadLineporque ReadStringno puede manejar el caso cuando la última línea de un archivo no termina con el carácter de nueva línea.

Samuel Hawksby-Robinson
fuente
37
De los documentos: "ReadLine es una primitiva de lectura de línea de bajo nivel. La mayoría de las personas que llaman deberían usar ReadBytes ('\ n') o ReadString ('\ n') o usar un escáner".
mdwhatcott
12
@mdwhatcott ¿por qué importa que sea una "primitiva de lectura de línea de bajo nivel"? ¿Cómo llega eso a la conclusión de que "La mayoría de las personas que llaman deberían usar ReadBytes ('\ n') o ReadString ('\ n') en su lugar o usar un Escáner"?
Charlie Parker
12
@CharlieParker: no estoy seguro, solo citando los documentos para agregar contexto.
mdwhatcott
11
De los mismos documentos ... "Si ReadString encuentra un error antes de encontrar un delimitador, devuelve los datos leídos antes del error y el error en sí mismo (a menudo io.EOF)". Entonces puede verificar el error io.EOF y saber que está listo.
eduncan911
1
Tenga en cuenta que una lectura o escritura puede fallar debido a una llamada interrumpida del sistema, lo que resulta en menos de la cantidad esperada de bytes leídos o escritos.
Justin Swanhart
599

En Go 1.1 y versiones posteriores, la forma más sencilla de hacerlo es con a bufio.Scanner. Aquí hay un ejemplo simple que lee líneas de un archivo:

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)
    }
}

Esta es la forma más limpia de leer una Readerlínea por línea.

Hay una advertencia: el escáner no funciona bien con líneas de más de 65536 caracteres. Si eso es un problema para ti, entonces probablemente deberías rodar el tuyo encima Reader.Read().

Stefan Arentz
fuente
40
Y dado que el OP solicitó escanear sobre un archivo, sería trivial primero file, _ := os.Open("/path/to/file.csv")y luego escanear sobre el identificador de archivo:scanner := bufio.NewScanner(file)
Evan Plumlee
14
No te olvides de hacerlo defer file.Close().
Kiril
13
El problema es Scanner.Scan () está limitado en un tamaño de búfer de 4096 [] bytes por línea. Obtendrá un bufio.ErrTooLongerror, que es bufio.Scanner: token too longsi la línea es demasiado larga. En cuyo caso, tendrá que usar bufio.ReaderLine () o ReadString ().
eduncan911
55
Solo mis $ 0.02 - esta es la respuesta más correcta en la página :)
sethvargo
55
Puede configurar Scanner para manejar líneas aún más largas utilizando su método Buffer (): golang.org/pkg/bufio/#Scanner.Buffer
Alex Robinson
78

Utilizar:

  • reader.ReadString('\n')
    • Si no le importa que la línea pueda ser muy larga (es decir, use mucha RAM). Mantiene el \nfinal de la cadena devuelta.
  • reader.ReadLine()
    • Si le importa limitar el consumo de RAM y no le importa el trabajo adicional de manejar el caso donde la línea es mayor que el tamaño del búfer del lector.

Probé las diversas soluciones sugeridas al escribir un programa para probar los escenarios que se identifican como problemas en otras respuestas:

  • Un archivo con una línea de 4 MB.
  • Un archivo que no termina con un salto de línea.

Encontre eso:

  • La Scannersolución no maneja largas colas.
  • La ReadLinesolución es compleja de implementar.
  • La ReadStringsolución es la más simple y funciona para largas colas.

Aquí hay un código que muestra cada solución, se puede ejecutar a través de 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]
}

Probé en:

  • Go versión go1.7 windows / amd64
  • go version go1.6.3 linux / amd64
  • go version go1.7.4 darwin / amd64

El programa de prueba produce:

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
fuente
99
El defer file.Close()debe ser después de la verificación de error; de lo contrario en caso de error entrará en pánico.
mlg
La solución de escáner maneja las líneas largas si la configura así. Ver: golang.org/pkg/bufio/#Scanner.Buffer
Inanc Gumus el
Debe verificar el error correctamente como se ve en los documentos: play.golang.org/p/5CCPzVTSj6, es decir, si err == io.EOF {break} else {return err}
Chuque
53

EDITAR: a partir de go1.1, la solución idiomática es usar bufio.Scanner

Escribí una forma de leer fácilmente cada línea de un archivo. La función Readln (* bufio.Reader) devuelve una línea (sans \ n) desde la estructura subyacente de bufio.Reader.

// 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
}

Puede usar Readln para leer cada línea de un archivo. El siguiente código lee cada línea de un archivo y envía cada línea a stdout.

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)
}

¡Salud!

Malcolm
fuente
14
Escribí esta respuesta antes de que saliera Go 1.1. Go 1.1 tiene un paquete de escáner en stdlib. eso proporciona la misma funcionalidad que mi respuesta. Recomendaría usar Scanner en lugar de mi respuesta ya que Scanner está en stdlib. ¡Feliz pirateo! :-)
Malcolm
30

Hay dos formas comunes de leer el archivo línea por línea.

  1. Use bufio.Scanner
  2. Utilice ReadString / ReadBytes / ... en bufio.Reader

En mi caso de prueba, ~ 250MB, ~ 2,500,000 líneas , bufio.Scanner (tiempo utilizado: 0.395491384s) es más rápido que bufio.Reader.ReadString (time_used: 0.446867622s).

Código fuente: https://github.com/xpzouying/go-practice/tree/master/read_file_line_by_line

Leer archivo usa 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
    }
}

Leer archivo 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
fuente
Tenga en cuenta que este bufio.Readerejemplo no leerá la última línea de un archivo si no termina con una nueva línea. ReadStringdevolverá tanto la última línea como io.EOFen este caso.
konrad el
18

Ejemplo de esta esencia

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
  }
}

pero esto da un error cuando hay una línea más grande que el búfer del escáner.

Cuando eso sucedió, lo que hago es usar reader := bufio.NewReader(inFile)create y concat mi propio buffer usando ch, err := reader.ReadByte()olen, err := reader.Read(myBuffer)

Otra forma que uso (reemplaza os.Stdin con el archivo como el anterior), este se concatena cuando las líneas son largas (isPrefix) e ignora las líneas vacías:


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
fuente
¿Quieres explicar por qué -1?
Kokizzu
Creo que es un poco complicado esta solución, ¿no?
Deccebal
10

También puede usar ReadString con \ n como separador:

  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
fuente
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                                                   
}                                    
ciber
fuente
1

En el siguiente código, leo los intereses de la CLI hasta que el usuario presiona enter y estoy usando Readline:

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
fuente
0

Me gusta la solución de Lzap, soy nuevo en Go, me gustaría pedirle a lzap pero no pude hacerlo. Todavía no tengo 50 puntos. Cambio un poco su solución y completo el código ...

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
    }
}

No estoy seguro de por qué necesito probar 'err' nuevamente, pero de todos modos podemos hacerlo. Pero, la pregunta principal es ... ¿por qué Go no produce errores con la oración => línea, err: = r.ReadString (10), dentro del bucle? Se define una y otra vez cada vez que se ejecuta el bucle. Evito esa situación con mi cambio, ¿algún comentario? Establecí la condición EOF en 'for' como similar a un While también. Gracias

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

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

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

Aquí hay un ejemplo con una función ReadFromStdin()similar, fmt.Scan(&name)pero toma todas las cadenas con espacios en blanco como: "Hola, mi nombre es ..."

var name string = ReadFromStdin()

println(name)
0DAYanc
fuente
0

Otro método es usar las bibliotecas io/ioutily stringspara leer los bytes de todo el archivo, convertirlos en una cadena y dividirlos usando un carácter " \n" (nueva línea) como delimitador, por ejemplo:

import (
    "io/ioutil"
    "strings"
)

func main() {
    bytesRead, _ := ioutil.ReadFile("something.txt")
    file_content := string(bytesRead)
    lines := strings.Split(file_content, "\n")
}

Técnicamente no estás leyendo el archivo línea por línea, sin embargo, puedes analizar cada línea usando esta técnica. Este método es aplicable a archivos más pequeños. Si está intentando analizar un archivo masivo, use una de las técnicas que se lee línea por línea.

pitón
fuente