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:

Paquetes principales para el procesamiento de archivos en Go os Núcleo: creación, lectura/escritura, eliminación, permisos io Interfaces genéricas Reader/Writer bufio Lectura/escritura con búfer, optimización del rendimiento filepath Manejo de rutas multiplataforma ioutil ⚠ Obsoleto en Go 1.16 → os / io Flujo de uso típico os.Open() bufio.Scanner Procesar datos defer Close()

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

package main

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

package main

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:

Tres formas de lectura de archivos Lectura de una sola vez os.ReadFile("file.txt") Apto para archivos pequeños (configuraciones, JSON, etc.) Apertura/cierre automáticos, la más sencilla Lectura línea por línea bufio.NewScanner(file) Apto para archivos grandes (logs, CSV, etc.) Amigable con la memoria, procesa línea por línea Lectura en flujo io.ReadAll(reader) Apto para respuestas de red, archivos ya abiertos Interfaz genérica Reader

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

package main

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

package main

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

package main

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

package main

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

package main

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

package main

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

package main

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

package main

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

package main

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

package main

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

package main

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 := "&#x1f4c4;"
                if entry.IsDir() {
                        mark = "&#x1f4c1;"
                }
                fmt.Printf("%s %s\n", mark, entry.Name())
        }

        // Eliminar directorio vacío
        os.Remove("newdir")

        // Eliminar recursivamente el directorio y todo su contenido (&#x26a0; 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

package main

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

package main

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

package main

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")

Explicació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).

Otras extensiones