Leia o arquivo de texto em um array de strings (e escreva)

100

A capacidade de ler (e escrever) um arquivo de texto dentro e fora de uma matriz de string é, creio, um requisito bastante comum. Também é muito útil ao iniciar com uma linguagem, eliminando a necessidade de acessar um banco de dados inicialmente. Existe um em Golang?
por exemplo

func ReadLines(sFileName string, iMinLines int) ([]string, bool) {

e

func WriteLines(saBuff[]string, sFilename string) (bool) { 

Eu preferiria usar um existente em vez de duplicar.

Brianoh
fonte
2
Use bufio.Scanner para ler as linhas de um arquivo, consulte stackoverflow.com/a/16615559/1136018 e golang.org/pkg/bufio
Jack Valmadre

Respostas:

124

No lançamento do Go1.1, existe uma API bufio.Scanner que pode ler facilmente as linhas de um arquivo. Considere o seguinte exemplo acima, reescrito com Scanner:

package main

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

// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(path string) ([]string, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var lines []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
    }
    return lines, scanner.Err()
}

// writeLines writes the lines to the given file.
func writeLines(lines []string, path string) error {
    file, err := os.Create(path)
    if err != nil {
        return err
    }
    defer file.Close()

    w := bufio.NewWriter(file)
    for _, line := range lines {
        fmt.Fprintln(w, line)
    }
    return w.Flush()
}

func main() {
    lines, err := readLines("foo.in.txt")
    if err != nil {
        log.Fatalf("readLines: %s", err)
    }
    for i, line := range lines {
        fmt.Println(i, line)
    }

    if err := writeLines(lines, "foo.out.txt"); err != nil {
        log.Fatalf("writeLines: %s", err)
    }
}
Kyle Lemons
fonte
124

Se o arquivo não for muito grande, isso pode ser feito com as funções ioutil.ReadFilee strings.Splitassim:

content, err := ioutil.ReadFile(filename)
if err != nil {
    //Do something
}
lines := strings.Split(string(content), "\n")

Você pode ler a documentação dos pacotes ioutil e strings .

Yanatan16
fonte
5
Ele lê o arquivo inteiro na memória, o que pode ser um problema se o arquivo for grande.
jergason
22
@Jergason, é por isso que ele começou sua resposta com "Se o arquivo não for muito grande ..."
laurent
9
ioutil pode ser importado como"io/ioutil"
Pramod
7
Observe as strings.Split acrescentará uma linha extra (uma string vazia) ao analisar exemplos de
bain
1
Para sua informação, no Windows, isso não removerá o \r. Portanto, você pode ter um \ranexo a cada elemento.
matfax
32

Não é possível atualizar a primeira resposta.
De qualquer forma, após o lançamento do Go1, houve algumas mudanças importantes, então eu atualizei conforme mostrado abaixo:

package main

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

// Read a whole file into the memory and store it as array of lines
func readLines(path string) (lines []string, err error) {
    var (
        file *os.File
        part []byte
        prefix bool
    )
    if file, err = os.Open(path); err != nil {
        return
    }
    defer file.Close()

    reader := bufio.NewReader(file)
    buffer := bytes.NewBuffer(make([]byte, 0))
    for {
        if part, prefix, err = reader.ReadLine(); err != nil {
            break
        }
        buffer.Write(part)
        if !prefix {
            lines = append(lines, buffer.String())
            buffer.Reset()
        }
    }
    if err == io.EOF {
        err = nil
    }
    return
}

func writeLines(lines []string, path string) (err error) {
    var (
        file *os.File
    )

    if file, err = os.Create(path); err != nil {
        return
    }
    defer file.Close()

    //writer := bufio.NewWriter(file)
    for _,item := range lines {
        //fmt.Println(item)
        _, err := file.WriteString(strings.TrimSpace(item) + "\n"); 
        //file.Write([]byte(item)); 
        if err != nil {
            //fmt.Println("debug")
            fmt.Println(err)
            break
        }
    }
    /*content := strings.Join(lines, "\n")
    _, err = writer.WriteString(content)*/
    return
}

func main() {
    lines, err := readLines("foo.txt")
    if err != nil {
        fmt.Println("Error: %s\n", err)
        return
    }
    for _, line := range lines {
        fmt.Println(line)
    }
    //array := []string{"7.0", "8.5", "9.1"}
    err = writeLines(lines, "foo2.txt")
    fmt.Println(err)
}
Bill.Zhuang
fonte
18

Você pode usar os.File (que implementa a interface io.Reader ) com o pacote bufio para isso. No entanto, esses pacotes são construídos com o uso de memória fixa em mente (não importa o tamanho do arquivo) e são bastante rápidos.

Infelizmente, isso torna a leitura de todo o arquivo na memória um pouco mais complicada. Você pode usar um bytes.Buffer para juntar as partes da linha se elas excederem o limite da linha. De qualquer forma, recomendo que você tente usar o leitor de linha diretamente no seu projeto (principalmente se não souber o tamanho do arquivo texto!). Mas se o arquivo for pequeno, o exemplo a seguir pode ser suficiente para você:

package main

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

// Read a whole file into the memory and store it as array of lines
func readLines(path string) (lines []string, err os.Error) {
    var (
        file *os.File
        part []byte
        prefix bool
    )
    if file, err = os.Open(path); err != nil {
        return
    }
    reader := bufio.NewReader(file)
    buffer := bytes.NewBuffer(make([]byte, 1024))
    for {
        if part, prefix, err = reader.ReadLine(); err != nil {
            break
        }
        buffer.Write(part)
        if !prefix {
            lines = append(lines, buffer.String())
            buffer.Reset()
        }
    }
    if err == os.EOF {
        err = nil
    }
    return
}

func main() {
    lines, err := readLines("foo.txt")
    if err != nil {
        fmt.Println("Error: %s\n", err)
        return
    }
    for _, line := range lines {
        fmt.Println(line)
    }
}

Outra alternativa pode ser usar io.ioutil.ReadAll para ler o arquivo completo de uma vez e depois fazer o corte por linha. Não dou um exemplo explícito de como escrever as linhas de volta no arquivo, mas isso é basicamente os.Create()seguido por um loop semelhante ao do exemplo (consulte Recursos main()).

tux21b
fonte
Obrigado por essa informação. Eu estava mais interessado em usar um pacote existente para fazer todo o trabalho, porque acho que é bastante útil. Por exemplo, desejo usar Go com persistência de dados sem usar um banco de dados inicialmente. Algumas línguas têm isso, eu acredito. por exemplo. Acho que Ruby tem Readlines que lêem uma série de strings (da memória) - não que eu seja um fã de Ruby. Acho que não é grande coisa, só não gosto de duplicação, mas talvez seja só eu que queira. De qualquer forma, escrevi um pacote para fazer isso e talvez coloque no github. Esses arquivos são geralmente muito pequenos.
brianoh
Se você deseja simplesmente persistir qualquer tipo de estrutura go (por exemplo, um array de strings, inteiros, mapas ou estruturas mais complicadas), você pode simplesmente usar o gob.Encode()para isso. O resultado é um arquivo binário em vez de um arquivo de texto separado por nova linha. Este arquivo pode conter todo tipo de dados, pode ser analisado de forma eficiente, o arquivo resultante será menor e você não terá que lidar com essas novas linhas e alocação dinâmica. Portanto, é provavelmente mais adequado para você se você apenas deseja manter algo para uso posterior com Go.
tux21b
O que eu quero é uma matriz de linhas de texto para que eu possa alterar qualquer linha (campo). Esses arquivos são muito pequenos. Quando as alterações são feitas, as strings de comprimento variável são eventualmente escritas de volta. É muito flexível e rápido para o que quero fazer. Preciso das novas linhas para separar as linhas (campos). Talvez haja uma maneira melhor, mas parece OK para meus objetivos no momento. Vou olhar o que você sugere mais tarde e talvez alterá-lo então.
brianoh
2
Observe que a partir de r58 (julho de 2011), o pacote de codificação / linha foi removido. "Sua funcionalidade agora está em bufio."
kristianp
4
func readToDisplayUsingFile1(f *os.File){
    defer f.Close()
    reader := bufio.NewReader(f)
    contents, _ := ioutil.ReadAll(reader)
    lines := strings.Split(string(contents), '\n')
}

ou

func readToDisplayUsingFile1(f *os.File){
    defer f.Close()
    slice := make([]string,0)

    reader := bufio.NewReader(f)

    for{

    str, err := reader.ReadString('\n')
    if err == io.EOF{
        break
    }

        slice = append(slice, str)
    }
Muhammad Soliman
fonte
1
quanto mais "moderno" todo mundo tenta dizer que é Go, mais ele se parece com um código de vinculação de biblioteca com o mínimo de 35 anos. : \ O fato de que simplesmente ler um arquivo de texto baseado em linha é uma bagunça apenas reforça que Go tem um longo caminho a .... ir ... para ter um propósito mais geral. Há MUITOS dados de texto e baseados em linha que ainda são processados ​​de forma muito eficiente em outros idiomas e plataformas. $ 0,02
ChrisH