Lendo um arquivo linha por linha no Go

335

Não consigo encontrar a file.ReadLinefunção no Go. Eu posso descobrir como escrever um rapidamente, mas estou me perguntando se estou ignorando algo aqui. Como se lê um arquivo linha por linha?

g06lin
fonte
7
A partir do Go1.1, o bufio.Scanner é a melhor maneira de fazer isso.
Malcolm

Respostas:

133

NOTA: A resposta aceita estava correta nas versões anteriores do Go. Veja a resposta mais votada contém a maneira idiomática mais recente de conseguir isso.

Existe a função ReadLine no pacote bufio.

Observe que, se a linha não couber no buffer de leitura, a função retornará uma linha incompleta. Se você sempre quiser ler uma linha inteira no seu programa com uma única chamada para uma função, precisará encapsular a ReadLinefunção na sua própria função, que chama ReadLineem um loop for.

bufio.ReadString('\n')não é totalmente equivalente a, ReadLineporque ReadStringé incapaz de lidar com o caso em que a última linha de um arquivo não termina com o caractere de nova linha.

Samuel Hawksby-Robinson
fonte
37
Dos documentos: "ReadLine é uma primitiva de leitura de linha de baixo nível. A maioria dos chamadores deve usar ReadBytes ('\ n') ou ReadString ('\ n') ou usar um scanner."
mdwhatcott
12
@mdwhatcott por que importa que é uma "primitiva de leitura de linha de baixo nível"? Como isso chega à conclusão de que "a maioria dos chamadores deve usar ReadBytes ('\ n') ou ReadString ('\ n') ou usar um scanner."?
Charlie Parker
12
@CharlieParker - Não tenho certeza, apenas citando os documentos para adicionar contexto.
precisa saber é o seguinte
11
Dos mesmos documentos .. "Se o ReadString encontrar um erro antes de localizar um delimitador, ele retornará os dados lidos antes do erro e o próprio erro (geralmente io.EOF)." Então você pode apenas verificar o erro io.EOF e saber que você está pronto.
eduncan911
1
Observe que uma leitura ou gravação pode falhar devido a uma chamada do sistema interrompida, o que resulta em menos do que o número esperado de bytes sendo lido ou gravado.
Justin Swanhart
599

No Go 1.1 e mais recente, a maneira mais simples de fazer isso é com um bufio.Scanner. Aqui está um exemplo simples que lê linhas de um arquivo:

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

Essa é a maneira mais limpa de ler uma Readerlinha por linha.

Há uma ressalva: o scanner não lida bem com linhas com mais de 65536 caracteres. Se isso é um problema para você, então você provavelmente deve rolar por conta própria Reader.Read().

Stefan Arentz
fonte
40
E já que o OP pediu para digitalizar sobre um arquivo, seria trivial para primeiro file, _ := os.Open("/path/to/file.csv")e depois digitalizar sobre o identificador de arquivo:scanner := bufio.NewScanner(file)
Evan Plumlee
14
Não esqueça disso defer file.Close().
Kiril
13
O problema é Scanner.Scan () é limitado em um tamanho de buffer de 4096 [] bytes por linha. Você receberá um bufio.ErrTooLongerro, ou seja, bufio.Scanner: token too longse a linha for muito longa. Nesse caso, você precisará usar bufio.ReaderLine () ou ReadString ().
precisa saber é o seguinte
5
Just my $ 0,02 - esta é a resposta correta mais na página :)
sethvargo
5
Você pode configurado Scanner para lidar com linhas ainda mais longos usando seu Buffer () método: golang.org/pkg/bufio/#Scanner.Buffer
Alex Robinson
78

Usar:

  • reader.ReadString('\n')
    • Se você não se importa que a linha possa ser muito longa (por exemplo, use muita memória RAM). Ele mantém o \nfinal da string retornado.
  • reader.ReadLine()
    • Se você se preocupa em limitar o consumo de RAM e não se importa com o trabalho extra de lidar com o caso em que a linha é maior que o tamanho do buffer do leitor.

Testei as várias soluções sugeridas escrevendo um programa para testar os cenários identificados como problemas em outras respostas:

  • Um arquivo com uma linha de 4 MB.
  • Um arquivo que não termina com uma quebra de linha.

Eu achei aquilo:

  • o Scanner solução não lida com longas filas.
  • A ReadLinesolução é complexa de implementar.
  • A ReadStringsolução é a mais simples e funciona para longas filas.

Aqui está o código que demonstra cada solução, que pode ser executado via 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]
}

Eu testei em:

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

O programa de teste gera:

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
fonte
9
O defer file.Close()deve ser após a verificação de erro; caso contrário, por erro, entrará em pânico.
mlg 22/07
A solução do scanner lida com as longas filas, se você a configurar assim. Veja: golang.org/pkg/bufio/#Scanner.Buffer
Inanc Gumus
Você deve verificar o erro corretamente como visto nos docs: play.golang.org/p/5CCPzVTSj6 ou seja, se err == io.EOF {pausa} else {err retorno}
Chuque
53

EDIT: A partir do go1.1, a solução idiomática é usar o bufio.Scanner

Eu escrevi uma maneira de ler facilmente cada linha de um arquivo. A função Readln (* bufio.Reader) retorna uma linha (sans \ n) da estrutura subjacente do 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
}

Você pode usar o Readln para ler todas as linhas de um arquivo. O código a seguir lê todas as linhas de um arquivo e gera cada linha no 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)
}

Felicidades!

Malcolm
fonte
14
Eu escrevi esta resposta antes do Go 1.1 ser lançado. O Go 1.1 possui um pacote de scanner no stdlib. que fornece a mesma funcionalidade da minha resposta. Eu recomendaria usar o Scanner em vez da minha resposta, pois o Scanner está no stdlib. Feliz hacking! :-)
Malcolm
30

Existem duas maneiras comuns de ler o arquivo linha por linha.

  1. Use o bufio.Scanner
  2. Use ReadString / ReadBytes / ... no bufio.Reader

No meu caso de teste, ~ 250MB, ~ 2.500.000 linhas , o bufio.Scanner (tempo usado: 0.395491384s) é mais rápido que o bufio.Reader.ReadString (time_used: 0.446867622s).

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

Leia o arquivo usando 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
    }
}

Leia o arquivo usando 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
fonte
Esteja ciente de que este bufio.Readerexemplo não lerá a última linha de um arquivo se não terminar com uma nova linha. ReadStringretornará a última linha e io.EOF, neste caso.
precisa
18

Exemplo desta essência

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

mas isso gera um erro quando há uma linha maior que o buffer do scanner.

Quando isso aconteceu, o que faço é reader := bufio.NewReader(inFile)criar e concatenar meu próprio buffer, usando ch, err := reader.ReadByte()oulen, err := reader.Read(myBuffer)

Outra maneira que eu uso (substitua os.Stdin pelo arquivo como acima), este concata quando as linhas são longas (isPrefix) e ignora as linhas vazias:


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
fonte
gostaria de explicar o porquê -1?
Kokizzu
Eu acho que é um pouco complicado demais essa solução, não é?
Decebal
10

Você também pode usar o ReadString com \ n como um 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
fonte
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                                                   
}                                    
cibernético
fonte
1

No código abaixo, leio os interesses da CLI até que o usuário aperte enter e estou usando o 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
fonte
0

Gosto da solução Lzap, sou novo no Go, gostaria de pedir para o lzap, mas não consegui fazê-lo ainda não tenho 50 pontos .. Troco um pouco a sua solução e complete o 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
    }
}

Não sei por que preciso testar 'err' novamente, mas de qualquer maneira podemos fazê-lo. Mas, a questão principal é .. por que o Go não produz erros com a frase => line, err: = r.ReadString (10), dentro do loop? É definido repetidamente toda vez que o loop é executado. Evito essa situação com a minha mudança, algum comentário? Defino a condição EOF em 'for' como semelhante a um While também. obrigado

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

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

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

Aqui está um exemplo com a função ReadFromStdin()que é como, fmt.Scan(&name)mas recebe todas as strings com espaços em branco como: "Olá, meu nome é ..."

var name string = ReadFromStdin()

println(name)
0DAYanc
fonte
0

Outro método é usar as bibliotecas io/ioutile stringspara ler os bytes do arquivo inteiro, convertê-los em uma sequência e dividi-los usando um caractere " \n" (nova linha) como delimitador, por exemplo:

import (
    "io/ioutil"
    "strings"
)

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

Tecnicamente, você não está lendo o arquivo linha por linha, mas é possível analisar cada linha usando essa técnica. Este método é aplicável a arquivos menores. Se você estiver tentando analisar um arquivo enorme, use uma das técnicas que lê linha por linha.

pythoner
fonte