El paquete os es la interfaz que tu programa utiliza para hablar con el sistema operativo. Cuando interactúas con el disco, no estás manipulando simplemente “archivos”, sino que estás gestionando descriptores de archivos (file descriptors) que el kernel del sistema operativo rastrea para ti.
Cuando ejecutas os.Open, el sistema operativo te devuelve un puntero a un os.File. Este objeto es la abstracción que implementa interfaces como io.Reader o io.Writer, permitiéndote usar cualquier función que acepte estas interfaces para leer o escribir datos. Si necesitas control total, como abrir un archivo en modo solo lectura, solo para añadir datos al final (O_APPEND) o para crear un archivo si no existe, os.OpenFile es tu herramienta, ya que te permite pasar banderas de control y permisos específicos. Por otro lado, os.Stat te permite consultar la metadata (tamaño, permisos, fechas de modificación) de un archivo mediante un FileInfo sin necesidad de abrirlo realmente, lo cual es mucho más eficiente para comprobaciones rápidas.
Sin embargo, el manejo de estos recursos tiene una regla de oro: si abres algo, debes cerrarlo. El uso de defer f.Close() inmediatamente después de verificar que la apertura fue exitosa es el estándar para asegurar que los descriptores de archivos se liberen, evitando la fuga de recursos que, en sistemas de alta carga, terminaría con el error too many open files. Pero ojo: si estás escribiendo, la gestión del cierre es aún más crítica de lo que parece.
package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
)
func main() {
dir := "logs_app"
filename := "system.log"
path := filepath.Join(dir, filename)
// 1. Crear la estructura de directorios de forma recursiva
// 0755: rwxr-xr-x (propietario puede leer/escribir/ejecutar, otros solo leer/ejecutar)
if err := os.MkdirAll(dir, 0755); err != nil {
fmt.Printf("Error creando directorios: %v\n", err)
return
}
// 2. Crear un archivo nuevo (o truncarlo si ya existe) para escritura inicial
f, err := os.Create(path)
if err != nil {
fmt.Printf("Error al crear archivo: %v\n", err)
return
}
// Usamos defer para asegurar la liberación del descriptor en caso de pánico o error temprano
defer f.Close()
_, err = f.WriteString("--- Inicio de Log ---\n")
if err != nil {
fmt.Printf("Error escribiendo: %v\n", err)
return
}
// IMPORTANTE: Para archivos de escritura, cerramos manualmente para capturar errores de flush
if err := f.Close(); err != nil {
fmt.Printf("Error al cerrar (escritura): %v\n", err)
return
}
// 3. Abrir el archivo en modo Append (añadir al final) y Read-Write
// os.O_APPEND: los writes ocurren al final del archivo
// os.O_WRONLY: solo escritura
fAppend, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("Error abriendo para append: %v\n", err)
return
}
_, err = fAppend.WriteString("Evento: Sistema iniciado correctamente\n")
if err != nil {
fmt.Printf("Error escribiendo en append: %v\n", err)
return
}
// Aquí el defer es seguro porque solo estamos añadiendo; si fallara, no perdemos el archivo original
defer fAppend.Close()
// 4. Consultar metadata sin abrir el archivo
info, err := os.Stat(path)
if err != nil {
fmt.Printf("Error obteniendo info: %v\n", err)
return
}
fmt.Printf("Archivo: %s, Tamaño: %d bytes\n", filename, info.Size())
// 5. Lectura final del contenido
fmt.Println("Contenido del archivo:")
readContent(path)
// 6. Limpieza: Eliminar el archivo y el directorio
fmt.Println("Limpiando...")
if err := os.Remove(path); err != nil {
fmt.Printf("Error eliminando archivo: %v\n", err)
}
if err := os.RemoveAll(dir); err != nil {
fmt.Printf("Error eliminando directorio: %v\n", err)
}
}
func readContent(path string) {
// Abrir solo para lectura
r, err := os.Open(path)
if err != nil {
fmt.Printf("Error abriendo para leer: %v\n", err)
return
}
defer r.Close()
_, err = io.Copy(os.Stdout, r)
if err != nil {
fmt.Printf("Error leyendo: %v\n", err)
}
}
Desglose del flujo
En el ejemplo, primero utilizamos os.MkdirAll para garantizar que la ruta logs_app exista. A diferencia de os.Mkdir, MkdirAll crea todos los directorios padres necesarios y no devuelve error si el directorio ya existe, lo cual es mucho más robusto para inicialización de servicios.
Cuando ejecutamos os.Create(path), el archivo se crea con permisos de lectura y escritura para el usuario, pero si ya existía, su contenido se trunca (se borra todo). Fíjate que inmediatamente llamamos a f.Close() de forma manual después de la primera escritura. Esto es vital: os.Create nos da un descriptor que queremos asegurar.
Luego, para la segunda escritura, usamos os.OpenFile con la bandera os.O_APPEND. Esto le dice al kernel que cada operación de escritura debe colocarse al final del archivo, sin importar si otros procesos han escrito entre medias. Es el comportamiento estándar para sistemas de logs.
Para obtener el tamaño del archivo (info.Size()), usamos os.Stat. Es una operación de metadatos que no requiere que mantengas un flujo de datos abierto, lo cual es más eficiente cuando solo necesitas saber si un archivo es demasiado grande o si existe. Finalmente, limpiamos todo con os.Remove y os.RemoveAll.
El error frecuente
Un error clásico al escribir archivos es confiar ciegamente en el defer f.Close() para operaciones de escritura críticas.
// MAL: Riesgo de pérdida de datos silenciosa
func saveConfig(data []byte) (err error) {
f, err := os.Create("config.bin")
if err != nil {
return err
}
defer f.Close() // El error de Close() será ignorado
_, err = f.Write(data)
return err
}
¿Por qué esto es peligroso? El sistema operativo suele usar buffers para optimizar la escritura en disco. Es posible que f.Write devuelva nil (éxito) porque los datos se enviaron al buffer del kernel, pero cuando el sistema intenta hacer el flush real al hardware durante el Close(), puede ocurrir un error (por ejemplo, disco lleno o error de hardware). Como el defer descarta el valor de retorno de Close(), tu función reportará un éxito cuando, en realidad, el archivo quedó corrupto o incompleto. Siempre cierra manualmente los archivos de escritura y verifica su error antes de considerar que la operación fue exitosa.
N° 113