Leer el archivo de texto en la matriz de cadenas (y escribir)

100

Creo que la capacidad de leer (y escribir) un archivo de texto dentro y fuera de una matriz de cadenas es un requisito bastante común. También es bastante útil cuando se comienza con un idioma, eliminando la necesidad de acceder inicialmente a una base de datos. ¿Existe uno en Golang?
p.ej

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

y

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

Preferiría usar uno existente en lugar de duplicarlo.

brianoh
fuente
2
Use bufio.Scanner para leer líneas de un archivo, consulte stackoverflow.com/a/16615559/1136018 y golang.org/pkg/bufio
Jack Valmadre

Respuestas:

124

A partir de la versión Go1.1, existe una API bufio.Scanner que puede leer fácilmente las líneas de un archivo. Considere el siguiente ejemplo de arriba, reescrito con 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)
    }
}
Limones Kyle
fuente
124

Si el archivo no es demasiado grande, esto se puede hacer con las funciones ioutil.ReadFiley strings.Splitasí:

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

Puede leer la documentación sobre paquetes ioutil y strings .

yanatan16
fuente
5
Lee todo el archivo en la memoria, lo que podría ser un problema si el archivo es grande.
jergason
22
@Jergason, por eso comenzó su respuesta con "Si el archivo no es demasiado grande ..."
Laurent
9
ioutil se puede importar como"io/ioutil"
Pramod
7
Note strings.Split agregará una línea adicional (una cadena vacía) al analizar el ejemplo de
bain
1
FYI, en Windows, esto no eliminará el \r. Por lo tanto, es posible que tenga \ranexado a cada elemento.
matfax
32

No se puede actualizar la primera respuesta.
De todos modos, después del lanzamiento de Go1, hay algunos cambios importantes, por lo que actualicé como se muestra a continuación:

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

Puede usar os.File (que implementa la interfaz io.Reader ) con el paquete bufio para eso. Sin embargo, esos paquetes se crean teniendo en cuenta el uso de memoria fijo (sin importar qué tan grande sea el archivo) y son bastante rápidos.

Desafortunadamente, esto hace que leer todo el archivo en la memoria sea un poco más complicado. Puede usar bytes.Buffer para unir las partes de la línea si exceden el límite de la línea. De todos modos, te recomiendo que pruebes a usar el lector de líneas directamente en tu proyecto (¡especialmente si no sabes qué tan grande es el archivo de texto!). Pero si el archivo es pequeño, el siguiente ejemplo puede ser suficiente para ti:

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

Otra alternativa podría ser usar io.ioutil.ReadAll para leer el archivo completo de una vez y luego hacer el corte por línea. No le doy un ejemplo explícito de cómo volver a escribir las líneas en el archivo, pero es básicamente un os.Create()ciclo seguido de un bucle similar al del ejemplo (ver main()).

tux21b
fuente
Gracias por esa información. Estaba más interesado en usar un paquete existente para hacer todo el trabajo, porque creo que es bastante útil. Por ejemplo, quiero usar Go con persistencia de datos sin usar una base de datos inicialmente. Algunos idiomas tienen esto, creo. p.ej. Creo que Ruby tiene Readlines que lee una serie de cadenas (de la memoria), no es que sea particularmente un fan de Ruby. No es gran cosa, supongo, simplemente no me gusta la duplicación, pero tal vez sea solo yo quien lo quiera. De todos modos, escribí un paquete para hacerlo y quizás lo ponga en github. Estos archivos suelen ser muy pequeños.
brianoh
Si simplemente desea conservar cualquier tipo de estructuras go (por ejemplo, una matriz de cadenas, enteros, mapas o estructuras más complicadas), simplemente puede usar el gob.Encode()para eso. El resultado es un archivo binario en lugar de un archivo de texto separado por saltos de línea. Este archivo puede contener todo tipo de datos, se puede analizar de manera eficiente, el archivo resultante será más pequeño y no tendrá que lidiar con esas nuevas líneas y asignación dinámica. Por lo tanto, probablemente sea más adecuado para usted si solo desea conservar algo para su uso posterior con Go.
tux21b
Lo que quiero es una matriz de líneas de texto para poder cambiar cualquier línea (campo). Estos archivos son muy pequeños. Cuando se realizan cambios, las cadenas de longitud variable finalmente se vuelven a escribir. Es muy flexible y rápido para lo que quiero hacer. Necesito las nuevas líneas para separar las líneas (campos). Quizás haya una manera mejor, pero parece que está bien para mis propósitos en este momento. Veré lo que sugieres más adelante y quizás lo cambie en ese momento.
brianoh
2
Tenga en cuenta que a partir de la versión r58 (julio de 2011), se eliminó el paquete encoding / line. "Su funcionalidad está ahora en 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')
}

o

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
fuente
1
cuanto más "moderno" todo el mundo intenta decir que es Go, más parece un código de enlace de biblioteca de 35 años como mínimo. : \ El hecho de que simplemente leer un archivo de texto basado en líneas sea un desastre solo refuerza que Go tiene un largo camino por recorrer ... ir ... para ser un propósito más general. Hay MUCHOS datos de texto basados ​​en líneas que todavía se procesan de manera muy eficiente en otros idiomas y plataformas. $ .02
ChrisH