¿Cómo leer / escribir desde / a un archivo usando Go?

284

He estado tratando de aprender Ir por mi cuenta, pero me ha sorprendido intentar leer y escribir en archivos normales.

Puedo llegar tan lejos inFile, _ := os.Open(INFILE, 0, 0), pero en realidad obtener el contenido del archivo no tiene sentido, porque la función de lectura toma []byteun parámetro.

func (file *File) Read(b []byte) (n int, err Error)
Seth Hoenig
fuente

Respuestas:

476

Hagamos una lista compatible con Go 1 de todas las formas de leer y escribir archivos en Go.

Debido a que la API de archivo ha cambiado recientemente y la mayoría de las otras respuestas no funcionan con Go 1. También omiten lo bufioque es importante en mi humilde opinión.

En los siguientes ejemplos, copio un archivo leyéndolo y escribiendo en el archivo de destino.

Comience con lo básico

package main

import (
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := fi.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := fo.Write(buf[:n]); err != nil {
            panic(err)
        }
    }
}

Aquí utilicé os.Openy os.Createque son envoltorios convenientes alrededor os.OpenFile. Por lo general, no necesitamos llamar OpenFiledirectamente.

Aviso de tratamiento de EOF. Readintenta completar bufcada llamada y devuelve un io.EOFerror si llega al final del archivo al hacerlo. En este caso bufaún se conservarán los datos. Las llamadas consiguientes a Readdevuelve cero como el número de bytes leídos e igual io.EOFque el error. Cualquier otro error provocará pánico.

Utilizando bufio

package main

import (
    "bufio"
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()
    // make a read buffer
    r := bufio.NewReader(fi)

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()
    // make a write buffer
    w := bufio.NewWriter(fo)

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := r.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := w.Write(buf[:n]); err != nil {
            panic(err)
        }
    }

    if err = w.Flush(); err != nil {
        panic(err)
    }
}

bufiosolo está actuando como un búfer aquí, porque no tenemos mucho que ver con los datos. En la mayoría de las otras situaciones (especialmente con archivos de texto) bufioes muy útil al proporcionarnos una buena API para leer y escribir de manera fácil y flexible, mientras maneja el almacenamiento en búfer detrás de escena.

Utilizando ioutil

package main

import (
    "io/ioutil"
)

func main() {
    // read the whole file at once
    b, err := ioutil.ReadFile("input.txt")
    if err != nil {
        panic(err)
    }

    // write the whole body at once
    err = ioutil.WriteFile("output.txt", b, 0644)
    if err != nil {
        panic(err)
    }
}

¡Muy fácil! Pero úselo solo si está seguro de que no está tratando con archivos grandes.

Mostafa
fuente
55
Para cualquier persona que se encuentre con esta pregunta, originalmente se hizo en 2009 antes de que se introdujeran estas bibliotecas, por lo tanto, ¡use esta respuesta como guía!
Seth Hoenig
1
De acuerdo con golang.org/pkg/os/#File.Write , cuando Write no ha escrito todos los bytes, devuelve un error. Por lo tanto, la verificación adicional en el primer ejemplo ( panic("error in writing")) no es necesaria.
ayke
15
Tenga en cuenta que estos ejemplos no comprueban el error devuelto por fo.Close (). Desde las páginas de manual de Linux close (2): No verificar el valor de retorno de close () es un error de programación común pero grave. Es muy posible que los errores en una operación anterior de escritura (2) se informen primero en el cierre final (). No verificar el valor de retorno al cerrar el archivo puede conducir a una pérdida silenciosa de datos. Esto se puede observar especialmente con NFS y con la cuota de disco.
Nick Craig-Wood el
12
Entonces, ¿qué es un archivo "grande"? 1 KB? 1MB? 1GB? ¿O "grande" depende del hardware de la máquina?
425nesp
3
@ 425nesp Lee todo el archivo en la memoria, por lo que depende de la cantidad de memoria disponible en la máquina en ejecución.
Mostafa
49

Esta es una buena versión:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}
Piotr
fuente
8
Esto almacena todo el archivo en la memoria. Como el archivo puede ser grande, es posible que eso no sea siempre lo que desea hacer.
user7610
9
Además, 0x777es falso. En cualquier caso, debería ser más como 0644o 0755(octal, no hexadecimal).
cnst
@cnst lo cambió a 0644 de 0x777
Trenton
31

Utilizando io.Copy

package main

import (
    "io"
    "log"
    "os"
)

func main () {
    // open files r and w
    r, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    defer r.Close()

    w, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    defer w.Close()

    // do the actual work
    n, err := io.Copy(w, r)
    if err != nil {
        panic(err)
    }
    log.Printf("Copied %v bytes\n", n)
}

Si no tiene ganas de reinventar la rueda, el io.Copyy io.CopyNpuede servir bien. Si verifica el origen de la función io.Copy, no es más que una de las soluciones de Mostafa (la 'básica', en realidad) empaquetada en la biblioteca Go. Sin embargo, están usando un buffer significativamente más grande que él.

user7610
fuente
55
una cosa vale la pena mencionar - para asegurarse de que el contenido del archivo se escribe en el disco, lo que necesita para su uso w.Sync()después de laio.Copy(w, r)
Shay Tsadok
Además, si escribe en un archivo ya existente, io.Copy()solo escribirá los datos con los que lo alimente, por lo que si el archivo existente tenía más contenido, no se eliminará, lo que puede provocar un archivo dañado.
Invidian
1
@Invidian Todo depende de cómo abra el archivo de destino. Si lo hace w, err := os.Create("output.txt"), lo que describe no sucede porque "Crear crea o trunca el archivo con nombre. Si el archivo ya existe, se trunca". golang.org/pkg/os/#Create .
user7610
Esta debería ser la respuesta correcta, ya que no reinventa la rueda sin tener que leer todo el archivo a la vez antes de leerlo.
Eli Davis
11

Con las versiones más nuevas de Go, leer / escribir en / desde el archivo es fácil. Para leer de un archivo:

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    data, err := ioutil.ReadFile("text.txt")
    if err != nil {
        return
    }
    fmt.Println(string(data))
}

Para escribir en un archivo:

package main

import "os"

func main() {
    file, err := os.Create("text.txt")
    if err != nil {
        return
    }
    defer file.Close()

    file.WriteString("test\nhello")
}

Esto sobrescribirá el contenido de un archivo (cree un nuevo archivo si no estaba allí).

Salvador Dalí
fuente
10

[]bytees un segmento (similar a una subcadena) de todo o parte de una matriz de bytes. Piense en el segmento como una estructura de valores con un campo de puntero oculto para que el sistema ubique y acceda a todo o parte de una matriz (el segmento), más campos para la longitud y la capacidad del segmento, a los que puede acceder utilizando las funciones len()y cap().

Aquí hay un kit de inicio que funciona para usted, que lee e imprime un archivo binario; necesitará cambiar el inNamevalor literal para referirse a un pequeño archivo en su sistema.

package main
import (
    "fmt";
    "os";
)
func main()
{
    inName := "file-rw.bin";
    inPerm :=  0666;
    inFile, inErr := os.Open(inName, os.O_RDONLY, inPerm);
    if inErr == nil {
        inBufLen := 16;
        inBuf := make([]byte, inBufLen);
        n, inErr := inFile.Read(inBuf);
        for inErr == nil {
            fmt.Println(n, inBuf[0:n]);
            n, inErr = inFile.Read(inBuf);
        }
    }
    inErr = inFile.Close();
}
PeterSO
fuente
9
La convención Go es verificar primero si hay errores y dejar que el código normal resida fuera del ifbloque
aceleración del
@Jurily: Si el archivo está abierto cuando se produce el error, ¿cómo lo cierra?
peterSO
10
@peterSO: use defer
James Antill
Pero, ¿por qué no se acepta un byte [256] y se acepta claramente el tonto y detallado (pero aparentemente no está mal) en Buuf: = make ([] byte, 256)?
Cardiff space man
7

Prueba esto:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}
vendedor
fuente
1
Esto funcionará si desea leer todo el archivo a la vez. Si el archivo es realmente grande o solo desea leer parte de él, puede que no sea lo que está buscando.
Evan Shaw el
3
¡Realmente deberías verificar el código de error, y no ignorarlo así!
hasen
77
Esto se ha trasladado al paquete ioutil ahora. Entonces sería ioutil.ReadFile ()
Christopher
Lo arreglé así que dice 0644
Joakim
1

Simplemente mirando la documentación, parece que debería declarar un búfer de tipo [] byte y pasarlo a leer que luego leerá hasta tantos caracteres y devolverá el número de caracteres realmente leídos (y un error).

Los documentos dicen

Lecturas de lectura hasta len (b) bytes del archivo. Devuelve el número de bytes leídos y un error, si lo hay. EOF se señala mediante un conteo cero con err establecido en EOF.

¿Eso no funciona?

EDITAR: Además, creo que quizás debería usar las interfaces Reader / Writer declaradas en el paquete bufio en lugar de usar el paquete os .

Hannes Ovrén
fuente
Usted tiene mi voto porque realmente reconoce lo que las personas reales ven cuando leen la documentación, en lugar de repetir lo que los que están acostumbrados a ir están RECORDADOS (no leen RECORDADOS) cuando leen la documentación de la función con la que ya están familiarizados.
Cardiff space man
1

El método de lectura toma un parámetro de byte porque es el búfer en el que leerá. Es un idioma común en algunos círculos y tiene sentido cuando lo piensas.

De esta manera, puede determinar cuántos bytes leerá el lector e inspeccionar el retorno para ver cuántos bytes se leyeron realmente y manejar los errores de manera adecuada.

Como otros han señalado en sus respuestas, bufio es probablemente lo que desea leer de la mayoría de los archivos.

Agregaré otra pista ya que es realmente útil. La mejor forma de leer una línea de un archivo no es mediante el método ReadLine, sino el método ReadBytes o ReadString.

Jeremy Wall
fuente