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:

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 lecturaos.O_WRONLY— solo escrituraos.O_RDWR— lectura y escrituraos.O_CREATE— crear si no existeos.O_TRUNC— truncar al abriros.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
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.