Lire le fichier texte dans un tableau de chaînes (et écrire)

100

La capacité de lire (et d'écrire) un fichier texte dans et hors d'un tableau de chaînes est, à mon avis, une exigence assez courante. Il est également très utile lorsque vous démarrez avec une langue en supprimant la nécessité d'accéder initialement à une base de données. En existe-t-il un à Golang?
par exemple

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

et

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

Je préférerais utiliser un existant plutôt que le dupliquer.

brianoh
la source
2
Utilisez bufio.Scanner pour lire les lignes d'un fichier, voir stackoverflow.com/a/16615559/1136018 et golang.org/pkg/bufio
Jack Valmadre

Réponses:

124

Depuis la version Go1.1, il existe une API bufio.Scanner qui peut facilement lire les lignes d'un fichier. Prenons l'exemple suivant ci-dessus, réécrit avec 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)
    }
}
Citrons Kyle
la source
124

Si le fichier n'est pas trop volumineux, cela peut être fait avec les fonctions ioutil.ReadFileet strings.Splitcomme ceci:

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

Vous pouvez lire la documentation sur les packages ioutil et strings .

yanatan16
la source
5
Il lit le fichier entier en mémoire, ce qui peut poser un problème si le fichier est volumineux.
jergason
22
@Jergason, c'est pourquoi il a commencé sa réponse par "Si le fichier n'est pas trop volumineux ..."
laurent
9
ioutil peut être importé en tant que"io/ioutil"
Pramod
7
Note strings.Split ajoutera une ligne supplémentaire (une chaîne vide) lors de l' analyse régulière des fichiers texte Posix exemple
bain
1
Pour info, sous Windows, cela ne supprimera pas le fichier \r. Vous pouvez donc ajouter un \rélément à chaque élément.
matfax
32

Impossible de mettre à jour la première réponse.
Quoi qu'il en soit, après la sortie de Go1, il y a eu des changements majeurs, j'ai donc mis à jour comme indiqué ci-dessous:

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
la source
18

Vous pouvez utiliser os.File (qui implémente l' interface io.Reader ) avec le package bufio pour cela. Cependant, ces packages sont construits avec une utilisation fixe de la mémoire à l'esprit (quelle que soit la taille du fichier) et sont assez rapides.

Malheureusement, cela rend la lecture du fichier entier dans la mémoire un peu plus compliquée. Vous pouvez utiliser un bytes.Buffer pour joindre les parties de la ligne si elles dépassent la limite de ligne. Quoi qu'il en soit, je vous recommande d'essayer d'utiliser le lecteur de ligne directement dans votre projet (surtout si vous ne savez pas quelle est la taille du fichier texte!). Mais si le fichier est petit, l'exemple suivant peut vous suffire:

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

Une autre alternative pourrait être d'utiliser io.ioutil.ReadAll pour lire le fichier complet à la fois et faire le découpage par ligne par la suite. Je ne vous donne pas d'exemple explicite sur la façon de réécrire les lignes dans le fichier, mais c'est essentiellement un os.Create()suivi d'une boucle similaire à celle de l'exemple (voir main()).

tux21b
la source
Merci pour cette information. J'étais plus intéressé par l'utilisation d'un package existant pour faire tout le travail, car je pense que c'est très utile. Par exemple, je souhaite utiliser Go avec persistance des données sans utiliser de base de données au départ. Certaines langues ont cela, je crois. par exemple. Je pense que Ruby a Readlines qui lit un tableau de chaînes (de mémoire) - non pas que je sois particulièrement fan de Ruby. Ce n'est pas grave je suppose, je n'aime tout simplement pas la duplication, mais peut-être que c'est juste moi qui le veux. Quoi qu'il en soit, j'ai écrit un paquet pour le faire et je vais peut-être le mettre sur github. Ces fichiers sont généralement très petits.
brianoh
Si vous voulez simplement conserver tout type de structures go (par exemple un tableau de chaînes, d'entiers, de cartes ou de structures plus compliquées), vous pouvez simplement utiliser le gob.Encode()pour cela. Le résultat est un fichier binaire au lieu d'un fichier texte séparé par une nouvelle ligne. Ce fichier peut contenir tout type de données, peut être analysé efficacement, le fichier résultant sera plus petit et vous n'aurez pas à gérer ces nouvelles lignes et l'allocation dynamique. C'est donc probablement mieux adapté pour vous si vous souhaitez simplement conserver quelque chose pour une utilisation ultérieure avec Go.
tux21b
Ce que je veux, c'est un tableau de lignes de texte pour que je puisse changer n'importe quelle ligne (champ). Ces fichiers sont très petits. Lorsque des modifications sont apportées, les chaînes de longueur variable sont finalement réécrites. C'est très flexible et rapide pour ce que je veux faire. J'ai besoin des nouvelles lignes pour séparer les lignes (champs). Il existe peut-être un meilleur moyen, mais cela me semble convenir pour le moment. Je regarderai ce que vous proposez plus tard et peut-être le changerai-je ensuite.
brianoh
2
Notez que depuis r58 (juillet 2011), le paquet encoding / line a été supprimé. "Sa fonctionnalité est maintenant dans 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
la source
1
plus tout le monde essaie de dire que Go est «moderne», plus il ressemble à un code de liaison de bibliothèque de 35 ans. : \ Le fait que la simple lecture d'un fichier texte basé sur des lignes soit un tel désordre ne fait que renforcer le fait que Go a un long chemin à parcourir pour .... aller ... pour être plus généraliste. Il existe BEAUCOUP de données textuelles et linéaires qui sont encore traitées de manière très efficace dans d'autres langages et plates-formes. $ .02
ChrisH