Procesamiento de archivos en Go
El procesamiento de archivos es una de las operaciones más comunes en Go: leer configuraciones, escribir registros (logs) y la persistencia de datos dependen de él. La biblioteca estándar de Go ofrece un conjunto de interfaces de E/S de archivos simples y potentes, que cubren desde lecturas y escrituras únicas hasta el procesamiento en flujo.
Hay 5 paquetes principales relacionados con el procesamiento de archivos, cada uno con su función:
Nota de versión:
ioutilEl paquete está obsoleto en Go 1.16; sus funciones se han migrado aos(comoos.ReadFile、os.WriteFile) yio(comoio.ReadAll). El código nuevo debe usar directamenteosyioel paquete.
1. Creación de archivos
os.CreateCrea un archivo nuevo. Si el archivo ya existe,se truncará (se vaciará su contenido). El objeto de archivo devuelto debe cerrarse para liberar los recursos del sistema:
Ejemplo
import (
"log"
"os"
)
func main() {
// os.Create crea el archivo (si existe, lo vacía)
file, err := os.Create("test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close() // defer asegura que el archivo se cierre al final de la función
// Escribir contenido para verificar que la creación fue exitosa
file.WriteString("Archivo creado con éxito\n")
log.Println("Archivo creado con éxito")
}
defer file.Close()es una buena práctica en las operaciones de archivos de Go.defergarantiza que, incluso si el código posterior genera un panic, el archivo se cierre correctamente, evitando fugas de descriptores de archivo.
2. Apertura y cierre de archivos
osEl paquete proporciona tres formas de abrir archivos, adecuadas para diferentes escenarios:
| Función | Modo | Descripción |
|---|---|---|
os.Open(name) |
Solo lectura | La forma más sencilla, abre solo lectura |
os.Create(name) |
Lectura/escritura + creación/truncado | Crea un archivo nuevo o vacía uno existente |
os.OpenFile(name, flag, perm) |
Personalizado | Puede especificar banderas como lectura/escritura, añadido (append), creación, etc. |
Ejemplo
import (
"fmt"
"os"
)
func main() {
// Método 1: abrir solo lectura
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error al abrir:", err)
return
}
defer file.Close()
fmt.Println("Archivo abierto correctamente")
// Método 2: OpenFile con modo personalizado
// os.O_WRONLY solo escritura
// os.O_CREATE crea si no existe
// os.O_APPEND modo añadir (append)
// os.O_TRUNC trunca si existe
f, err := os.OpenFile("log.txt",
os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Println("Error al abrir:", err)
return
}
defer f.Close()
f.WriteString("Añadir una línea de registro\n")
}
Descripción de las banderas de OpenFile
| Bandera | Descripción |
|---|---|
os.O_RDONLY |
Solo lectura (predeterminado) |
os.O_WRONLY |
Solo escritura |
os.O_RDWR |
Lectura/escritura |
os.O_APPEND |
Modo añadir, el contenido se escribe al final del archivo |
os.O_CREATE |
Crea el archivo si no existe |
os.O_TRUNC |
Vacía el contenido del archivo al abrirlo |
os.O_EXCL |
Junto con CREATE, produce un error si el archivo ya existe |
3. Lectura de archivos
Go ofrece tres formas principales de leer archivos; elige según el tamaño del archivo y las necesidades de procesamiento:
3.1 Lectura de una sola vez (recomendado para archivos pequeños)
os.ReadFileEs la forma más sencilla: abre, lee y cierra el archivo automáticamente, todo en un solo paso:
Ejemplo
import (
"fmt"
"log"
"os"
)
func main() {
// Leer todo el archivo de una sola vez (Go 1.16+)
data, err := os.ReadFile("config.json")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
3.2 Lectura línea por línea (recomendado para archivos grandes)
Para archivos grandes, usabufio.Scannerpara procesar línea por línea, evitando cargar todo en memoria:
Ejemplo
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("large-file.log")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Escanear línea por línea
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
line := scanner.Text() // Obtener el contenido de la línea actual
fmt.Printf("Línea %d: %s\n", lineNum, line)
}
// Comprobar si ocurrió un error durante el escaneo
if err := scanner.Err(); err != nil {
log.Fatal("Error de lectura:", err)
}
}
Nota:
bufio.ScannerLa longitud de línea máxima predeterminada es de 64 KB. Si el archivo contiene líneas demasiado largas, debe, antes de llamar aScan()configurar de antemanoscanner.Buffer(make([]byte, 0), maxSize)para aumentar el búfer.
3.3 Leer usando io.ReadAll
Cuando ya tienes unio.Reader(como una respuesta de red o un archivo ya abierto), puedes usario.ReadAll:
Ejemplo
import (
"fmt"
"io"
"log"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Leer todos los datos desde el Reader
data, err := io.ReadAll(file)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
4. Escritura de archivos
Go ofrece múltiples formas de escritura, desde la escritura única simple hasta la escritura con búfer de alto rendimiento:
4.1 Escritura de una sola vez
os.WriteFileEscribe los datos en el archivo de una sola vez (sobrescribe el contenido original):
Ejemplo
import (
"log"
"os"
)
func main() {
content := []byte("Hello, Go!\nEsta es la segunda línea\n")
// 0644: el propietario puede leer/escribir, otros usuarios solo lectura
err := os.WriteFile("output.txt", content, 0644)
if err != nil {
log.Fatal(err)
}
log.Println("Escritura exitosa")
}
4.2 Escritura usando el objeto File
Escribir a través del objeto de archivo permite escribir contenido en varias veces:
Ejemplo
import (
"fmt"
"log"
"os"
)
func main() {
file, err := os.Create("output.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Método 1: escribir una cadena
file.WriteString("Escribir cadena directamente\n")
// Método 2: escribir un slice de bytes
data := []byte("Escribir slice de bytes\n")
file.Write(data)
// Método 3: escritura con formato
fmt.Fprintf(file, "Escritura formateada: %d + %d = %d\n", 3, 4, 7)
}
4.3 Escritura con búfer (recomendado para grandes volúmenes de datos)
bufio.WriterPrimero escribe los datos en un búfer de memoria y, cuando se acumulan suficientes, los escribe en bloque al disco, reduciendo significativamente el número de operaciones de E/S.Asegúrese de llamar a antes de finalizar.Flush():
Ejemplo
import (
"bufio"
"log"
"os"
)
func main() {
file, err := os.Create("buffered-output.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Crear un escritor con búfer
writer := bufio.NewWriter(file)
// Escribir varias líneas (primero van al búfer, sin escribir al disco inmediatamente)
for i := 0; i < 1000; i++ {
writer.WriteString("Esta es la " + itoa(i) + " línea\n")
}
// Flush escribe los datos restantes del búfer en el archivo
if err := writer.Flush(); err != nil {
log.Fatal("Error al vaciar el búfer:", err)
}
log.Println("Escritura con búfer completada")
}
// Función auxiliar simple para convertir int a string
func itoa(n int) string {
return fmt.Sprintf("%d", n)
}
5. Escritura añadiendo al final del archivo (append)
Usaos.O_APPENDla bandera para añadir contenido al final del archivo sin sobrescribir los datos existentes:
Ejemplo
import (
"log"
"os"
"time"
)
func main() {
// O_APPEND: modo añadir (append)
// O_CREATE: crea si no existe
// O_WRONLY: solo escritura
file, err := os.OpenFile("app.log",
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Cada ejecución añade, no sobrescribe
timestamp := time.Now().Format("2006-01-02 15:04:05")
file.WriteString("[" + timestamp + "] Aplicación iniciada\n")
}
Comparación:
os.Createvaciará el contenido existente;os.OpenFilejunto conO_APPENDañadirá contenido. En escenarios de registro (logs), asegúrate de usar el modo añadir.
6. Eliminación y renombrado de archivos
Ejemplo
import (
"log"
"os"
)
func main() {
// Renombrar / mover archivo
err := os.Rename("old.txt", "new.txt")
if err != nil {
log.Fatal(Error al renombrar:, err)
}
log.Println(Renombrado exitoso)
// Eliminar archivo
err = os.Remove("temp.txt")
if err != nil {
log.Fatal(Error al eliminar:, err)
}
log.Println(Eliminado exitoso)
}
7. Información y comprobación de archivos
7.1 Obtener información del archivo
Ejemplo
import (
"fmt"
"log"
"os"
)
func main() {
info, err := os.Stat("test.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(Nombre de archivo:, info.Name())
fmt.Println(Tamaño:, info.Size(), bytes)
fmt.Println(Permisos:, info.Mode())
fmt.Println(Fecha de modificación:, info.ModTime())
fmt.Println(¿Es directorio?:, info.IsDir())
}
7.2 Comprobar si existe un archivo
Go no tiene una función específica de "existencia de archivo"; se determina medianteos.Stat + os.IsNotExistla comprobación:
Ejemplo
import (
"fmt"
"os"
)
// fileExists verifica si el archivo existe
func fileExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
func main() {
if fileExists("test.txt") {
fmt.Println(El archivo existe)
} else {
fmt.Println(El archivo no existe)
}
}
8. Operaciones con directorios
Ejemplo
import (
"fmt"
"log"
"os"
)
func main() {
// Crear un solo directorio
err := os.Mkdir("newdir", 0755)
if err != nil {
log.Fatal(err)
}
// Crear directorios de varios niveles recursivamente (se crean automáticamente los directorios padre si no existen)
err = os.MkdirAll("path/to/deep/dir", 0755)
if err != nil {
log.Fatal(err)
}
// Leer contenido del directorio
entries, err := os.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
mark := "📄"
if entry.IsDir() {
mark = "📁"
}
fmt.Printf("%s %s\n", mark, entry.Name())
}
// Eliminar directorio vacío
os.Remove("newdir")
// Eliminar recursivamente el directorio y todo su contenido (⚠ usar con precaución)
os.RemoveAll("path")
}
9. Operaciones avanzadas
9.1 Copia de archivos
Usaio.Copypara copiar datos de manera eficiente entre dos archivos:
Ejemplo
import (
"io"
"log"
"os"
)
// copyFile función genérica para copiar archivos
func copyFile(src, dst string) (int64, error) {
srcFile, err := os.Open(src)
if err != nil {
return 0, err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return 0, err
}
defer dstFile.Close()
// io.Copy gestiona automáticamente el búfer, copia eficiente
return io.Copy(dstFile, srcFile)
}
func main() {
n, err := copyFile("source.txt", "destination.txt")
if err != nil {
log.Fatal(err)
}
log.Printf(Copia completada, %d bytes en total, n)
}
9.2 Archivos y directorios temporales
Ejemplo
import (
"fmt"
"log"
"os"
)
func main() {
// Crear archivo temporal (nombre automático, prefijo "app-")
tmpFile, err := os.CreateTemp("", "app-*.txt")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpFile.Name()) // Limpiar después de usarlo
fmt.Println(Archivo temporal:, tmpFile.Name())
// Crear directorio temporal
tmpDir, err := os.MkdirTemp("", "app-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tmpDir) // Limpiar después de usarlo
fmt.Println(Directorio temporal:, tmpDir)
}
9.3 Recorrido recursivo de directorios
filepath.WalkSe pueden recorrer recursivamente todos los archivos y subdirectorios del árbol de directorios:
Ejemplo
import (
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
root := "." // Comenzar el recorrido desde el directorio actual
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err // Al encontrar un error, se puede optar por omitirlo o retornar
}
// Imprimir ruta y tamaño
size := ""
if !info.IsDir() {
size = fmt.Sprintf(" (%d bytes)", info.Size())
}
fmt.Println(path + size)
return nil
})
if err != nil {
log.Fatal(err)
}
}
9.4 Concatenación de rutas multiplataforma
Nunca uses concatenación de cadenas para rutas(comodir + "/" + file), usafilepath.Joinpara garantizar compatibilidad multiplataforma:
// 错误:Windows 上路径分隔符是 \
path := "dir" + "/" + "file.txt"
// 正确:自动适配当前操作系统
path := filepath.Join("dir", "file.txt")
Tabla de referencia rápida por escenarios
| Escenario | Método recomendado | Código clave |
|---|---|---|
| Leer archivos pequeños | os.ReadFile |
data, err := os.ReadFile("file.txt") |
| Leer archivos grandes línea por línea | bufio.Scanner |
scanner := bufio.NewScanner(file) |
| Escribir archivos pequeños | os.WriteFile |
os.WriteFile("f.txt", data, 0644) |
| Escritura de grandes cantidades de datos | bufio.Writer |
writer := bufio.NewWriter(file) |
| Agregar registros (logs) | os.OpenFile + O_APPEND |
os.OpenFile("log", os.O_APPEND|os.O_WRONLY, 0644) |
| Copia de archivos | io.Copy |
io.Copy(dst, src) |
| Recorrer directorios | filepath.Walk |
filepath.Walk(".", callback) |
| Concatenación de rutas | filepath.Join |
filepath.Join("dir", "file.txt") |
| Verificar existencia de archivo | os.Stat + os.IsNotExist |
_, err := os.Stat(path) |
| Crear archivo temporal | os.CreateTemp |
os.CreateTemp("", "prefix-*.txt") |
Otras extensionesExplicación de permisos de archivo:
0644indica que el propietario puede leer y escribir (6=4+2), y el grupo y otros usuarios solo lectura (4). Para directorios normalmente se usa0755(agrega permiso de ejecución, de lo contrario no se puede entrar al directorio).