Ficheros y operaciones de entrada/salida

Intermedio
Go
Go
Actualizado: 03/04/2026

Leer y escribir ficheros completos

Para ficheros pequeños cuyo contenido cabe en memoria, os.ReadFile y os.WriteFile son la forma más directa:

Ficheros y entrada/salida en Go: lectura, escritura y defer Close

package main

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

func main() {
    // Leer un fichero completo
    contenido, err := os.ReadFile("config.txt")
    if err != nil {
        log.Fatalf("Error al leer fichero: %v", err)
    }
    fmt.Println(string(contenido))

    // Escribir un fichero (crea o sobreescribe)
    datos := []byte("línea 1\nlínea 2\nlínea 3\n")
    if err := os.WriteFile("resultado.txt", datos, 0644); err != nil {
        log.Fatalf("Error al escribir fichero: %v", err)
    }
    fmt.Println("Fichero escrito correctamente")
}

El tercer argumento de WriteFile es la máscara de permisos Unix: 0644 significa lectura/escritura para el propietario, solo lectura para el resto.

Lectura eficiente línea a línea: bufio.Scanner

Para ficheros grandes, leer todo el contenido a la vez puede consumir demasiada memoria. bufio.Scanner lee línea a línea con un buffer interno:

func contarLineas(ruta string) (int, error) {
    fichero, err := os.Open(ruta)
    if err != nil {
        return 0, fmt.Errorf("no se pudo abrir %q: %w", ruta, err)
    }
    defer fichero.Close()

    scanner := bufio.NewScanner(fichero)
    contador := 0
    for scanner.Scan() {
        linea := scanner.Text()
        if linea != "" {
            contador++
        }
    }

    if err := scanner.Err(); err != nil {
        return 0, fmt.Errorf("error al leer: %w", err)
    }
    return contador, nil
}

Importante: siempre verifica scanner.Err() al finalizar; un final de fichero normal devuelve nil, pero un error de E/S devuelve el error real.

Escritura con buffer: bufio.Writer

bufio.Writer acumula escrituras en memoria y las envía al disco en bloques, reduciendo las llamadas al sistema operativo:

func escribirCSV(ruta string, registros [][]string) error {
    fichero, err := os.Create(ruta)
    if err != nil {
        return err
    }
    defer fichero.Close()

    escritor := bufio.NewWriter(fichero)
    for _, registro := range registros {
        for i, campo := range registro {
            if i > 0 {
                escritor.WriteString(",")
            }
            escritor.WriteString(campo)
        }
        escritor.WriteString("\n")
    }

    return escritor.Flush() // CRÍTICO: vaciar el buffer antes de cerrar
}

Si no llamas a Flush(), los últimos bytes del buffer podrían no escribirse en el disco.

Abrir ficheros con control total: os.OpenFile

// Abrir para añadir al final (append)
fichero, err := os.OpenFile("log.txt",
    os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
    log.Fatal(err)
}
defer fichero.Close()

fmt.Fprintln(fichero, "nueva entrada de log")

Flags comunes:

  • os.O_RDONLY — solo lectura
  • os.O_WRONLY — solo escritura
  • os.O_RDWR — lectura y escritura
  • os.O_CREATE — crear si no existe
  • os.O_TRUNC — truncar al abrir
  • os.O_APPEND — añadir al final

Las interfaces io.Reader e io.Writer

Estas dos interfaces son la base de toda la E/S en Go:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

Cualquier tipo que las implemente puede usarse de forma intercambiable. Esto permite componer operaciones:

// Copiar datos de cualquier Reader a cualquier Writer
io.Copy(os.Stdout, strings.NewReader("hola desde un string\n"))

// Leer todo el contenido de un Reader
contenido, err := io.ReadAll(resp.Body)

// Tipos que implementan io.Reader
var _ io.Reader = (*os.File)(nil)
var _ io.Reader = (*bytes.Buffer)(nil)
var _ io.Reader = (*strings.Reader)(nil)

Manipular directorios

// Listar entradas de un directorio
entradas, err := os.ReadDir("./datos")
if err != nil {
    log.Fatal(err)
}
for _, entrada := range entradas {
    tipo := "fichero"
    if entrada.IsDir() {
        tipo = "directorio"
    }
    fmt.Printf("%-20s %s\n", entrada.Name(), tipo)
}

// Crear directorios (incluyendo intermedios)
os.MkdirAll("datos/2026/marzo", 0755)

// Información de un fichero
info, err := os.Stat("datos.txt")
if err != nil {
    if os.IsNotExist(err) {
        fmt.Println("El fichero no existe")
    }
} else {
    fmt.Printf("Tamaño: %d bytes, modificado: %s\n",
        info.Size(), info.ModTime().Format("2006-01-02"))
}

// Renombrar o mover
os.Rename("viejo.txt", "nuevo.txt")

// Eliminar
os.Remove("fichero.txt")    // solo ficheros o directorios vacíos
os.RemoveAll("directorio/") // elimina árbol completo

Ficheros temporales

// Crear fichero temporal en el directorio del sistema
tmp, err := os.CreateTemp("", "prefijo-*.json")
if err != nil {
    log.Fatal(err)
}
defer os.Remove(tmp.Name()) // limpiar al terminar

fmt.Println("Fichero temporal:", tmp.Name())
json.NewEncoder(tmp).Encode(map[string]string{"clave": "valor"})
tmp.Close()

// Directorio temporal
dir, err := os.MkdirTemp("", "proceso-*")
defer os.RemoveAll(dir)
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Go es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Go

Explora más contenido relacionado con Go y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Leer y escribir ficheros completos con os.ReadFile y os.WriteFile.
  • Usar bufio.Scanner para leer ficheros grandes línea a línea de forma eficiente.
  • Escribir con buffer usando bufio.Writer y vaciar con Flush.
  • Comprender las interfaces io.Reader e io.Writer y cómo componerlas.
  • Manipular directorios con os.ReadDir, os.MkdirAll y os.Remove.
  • Crear y usar ficheros temporales con os.CreateTemp.