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.Scandevuelvefalsetanto al final del archivo como ante un error, y soloErrlos 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.ScanRunesproduce 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:
| Flag | Significado |
|---|---|
os.O_RDONLY, os.O_WRONLY, os.O_RDWR | abrir para leer, escribir o ambas cosas (elige uno) |
os.O_CREATE | crear el archivo si no existe |
os.O_TRUNC | vaciar el archivo al abrirlo |
os.O_APPEND | cada escritura va al final |
os.O_EXCL | con 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
| Tarea | Función |
|---|---|
| crear un directorio | os.Mkdir(path, 0o755) |
| crear una ruta con sus padres | os.MkdirAll(path, 0o755) |
| listar un directorio | os.ReadDir(path) |
| recorrer un árbol | filepath.WalkDir(root, fn) |
| borrar un archivo o un directorio vacío | os.Remove(path) |
| borrar un árbol | os.RemoveAll(path) |
| renombrar o mover | os.Rename(old, new) |
| archivo o directorio temporal | os.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix") |
| unir partes de una ruta | filepath.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 cadaRead,WriteoCloseposterior sobre él devuelveinvalid 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
Flushen unbufio.Writer. Falta el final del archivo. - Ignorar el error de
Closedespué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.
644no es0o644. Go lee644como número decimal, que es0o1204y 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".