Menu

Leer y escribir archivos en Golang: os, bufio y directorios

Cómo leer y escribir archivos en Go: os.ReadFile y os.WriteFile, leer línea a línea con bufio.Scanner, añadir al final con os.OpenFile, comprobar si un archivo existe y trabajar con directorios.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Leer y escribir un archivo completo

os.WriteFile y os.ReadFile cubren la mayoría de necesidades. Abren, escriben o leen, y cierran en una sola llamada.

Los ejemplos de esta página trabajan en un directorio temporal creado con os.MkdirTemp y lo eliminan con defer os.RemoveAll(dir), así que no dejan nada atrás. En tu propio código, una ruta relativa como "config.json" se resuelve respecto al directorio de trabajo del proceso, que no tiene por qué ser el directorio del archivo fuente ni del binario.

os.WriteFile crea el archivo si hace falta y lo trunca si ya existe. El tercer argumento es el permiso Unix para un archivo recién creado: 0o644 significa que el propietario puede leer y escribir y el resto solo leer. Se ignora si el archivo ya existe, y la umask del proceso puede quitar bits.

os.ReadFile lo lee todo en memoria. Es lo adecuado para archivos de configuración y entradas pequeñas, y lo inadecuado para un log de varios gigabytes.

Leer línea a línea con bufio.Scanner

Para archivos grandes, o cuando de todos modos quieres líneas, usa bufio.Scanner. Lee por bloques y te entrega una línea cada vez, sin el salto de línea.

Tres detalles:

  • Comprueba sc.Err() después del bucle. Scan devuelve false tanto al final del archivo como ante un error, y solo Err los distingue.
  • El límite de 64 KB por línea. Por defecto, una sola línea de más de 64 KB detiene el scanner con bufio.Scanner: token too long. Para archivos con líneas largas (JSON minificado, algunos logs), sube el límite antes del bucle: sc.Buffer(make([]byte, 1024*1024), 10*1024*1024).
  • Otras unidades. sc.Split(bufio.ScanWords) produce palabras; bufio.ScanRunes produce caracteres.

Para leer un stream en bloques de tamaño fijo en lugar de líneas, usa f.Read(buf) en un bucle o io.Copy hacia otro writer.

Escribir: os.Create, os.OpenFile y añadir al final

os.Create(name) abre un archivo para escribir, creándolo o truncándolo. os.OpenFile da control total mediante flags:

FlagSignificado
os.O_RDONLY, os.O_WRONLY, os.O_RDWRabrir para leer, escribir o ambas cosas (elige uno)
os.O_CREATEcrear el archivo si no existe
os.O_TRUNCvaciar el archivo al abrirlo
os.O_APPENDcada escritura va al final
os.O_EXCLcon O_CREATE: fallar si el archivo ya existe

os.Open(name) equivale a OpenFile(name, O_RDONLY, 0). os.Create(name) equivale a OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666).

Al escribir, el error de Close importa. Algunos sistemas de archivos solo informan de los fallos de escritura al cerrar, así que un defer f.Close() a secas puede ocultar datos perdidos. En los archivos que escribes, comprueba Close explícitamente como hace appendLine. En los archivos que solo lees, defer f.Close() está bien.

fmt.Fprintln y el resto de funciones de impresión de fmt aceptan cualquier io.Writer, incluido un archivo. Un bufio.Writer agrupa las escrituras pequeñas en memoria. Olvidar w.Flush() es un bug clásico: el programa termina con normalidad y los últimos kilobytes nunca llegan al archivo.

¿Existe el archivo?

Go no tiene os.Exists. Llama a os.Stat e inspecciona el error:

errors.Is(err, fs.ErrNotExist) es el idioma actual. Sustituye al antiguo os.IsNotExist(err), que no ve a través de los errores envueltos.

Comprobar antes de abrir suele ser innecesario y propenso a carreras: el archivo puede aparecer o desaparecer entre la comprobación y la apertura. Normalmente basta con abrirlo y manejar el fs.ErrNotExist de la apertura. Para crear un archivo solo si todavía no existe, usa O_CREATE|O_EXCL, que hace de la comprobación y la creación un único paso atómico.

Directorios

TareaFunción
crear un directorioos.Mkdir(path, 0o755)
crear una ruta con sus padresos.MkdirAll(path, 0o755)
listar un directorioos.ReadDir(path)
recorrer un árbolfilepath.WalkDir(root, fn)
borrar un archivo o un directorio vacíoos.Remove(path)
borrar un árbolos.RemoveAll(path)
renombrar o moveros.Rename(old, new)
archivo o directorio temporalos.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix")
unir partes de una rutafilepath.Join(a, b, c)

Usa path/filepath para las rutas del sistema de archivos: usa el separador correcto para el sistema operativo (\ en Windows). El paquete path es para rutas separadas por barras, como las URLs.

Go 1.24 añadió también os.Root (os.OpenRoot(dir)), que solo abre archivos dentro de un directorio y rechaza las rutas que escapan de él con .. o enlaces simbólicos. Úsalo cuando los nombres de archivo vengan de los usuarios.

Errores comunes

  • No comprobar los errores. Todas estas llamadas pueden fallar. Un archivo que no se pudo abrir es nil, y cada Read, Write o Close posterior sobre él devuelve invalid argument, lo que oculta la causa real (el archivo no existía o se denegó el permiso).
  • Olvidar sc.Err() después de un bucle de scan. Un error de lectura parece el final del archivo.
  • Olvidar Flush en un bufio.Writer. Falta el final del archivo.
  • Ignorar el error de Close después de escribir. Los errores de escritura pueden aparecer solo ahí.
  • defer f.Close() dentro de un bucle sobre muchos archivos. Los archivos siguen abiertos hasta que la función retorna y te puedes quedar sin descriptores de archivo. Mueve el cuerpo a una función para que cada archivo se cierre en cada iteración.
  • Permisos escritos en decimal. 644 no es 0o644. Go lee 644 como número decimal, que es 0o1204 y activa bits extraños.

Preguntas frecuentes

¿Cómo leo un archivo entero en un string en Go?

data, err := os.ReadFile("notes.txt") devuelve el contenido como []byte; conviértelo con string(data). Abre, lee y cierra el archivo por ti. Úsalo para archivos que caben de sobra en memoria; para archivos grandes, lee línea a línea con bufio.Scanner.

¿Cómo leo un archivo línea por línea en Go?

Abre el archivo con os.Open, haz defer f.Close(), envuélvelo en bufio.NewScanner(f), recórrelo con for sc.Scan() { line := sc.Text() } y comprueba sc.Err() después del bucle. Las líneas de más de 64 KB hacen fallar al scanner con token too long salvo que amplíes su buffer con sc.Buffer.

¿Cómo añado contenido al final de un archivo en Go?

Ábrelo con os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644), escribe y comprueba el error de Close. O_CREATE crea el archivo si no existe, y O_APPEND hace que cada escritura vaya al final.

¿Cómo compruebo si un archivo existe en Go?

Llama a os.Stat(path) y comprueba el error con errors.Is(err, fs.ErrNotExist). Un error nil significa que existe. Cualquier otro error (permiso denegado, por ejemplo) significa que no puedes saberlo, así que trátalo aparte en lugar de tomarlo como "no existe".

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR