← Volver al listado de tecnologías

I/O, OS y Manejo de Archivos

Por: Artiko
goioosarchivosbufio

I/O, OS y Manejo de Archivos

Go modela toda entrada/salida alrededor de dos interfaces fundamentales: io.Reader e io.Writer. Esta abstraccion permite que archivos, conexiones de red, buffers y HTTP compartan la misma API.

io.Reader e io.Writer

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

Cualquier tipo que implemente estos metodos puede usarse donde se espere un Reader o Writer.

Implementa ReaderImplementa WriterAmbos
os.Fileos.Fileos.File
strings.Readerbytes.Bufferbytes.Buffer
http.Request.Bodyhttp.ResponseWriternet.Conn
bufio.Readerbufio.Writeros.Stdout

Leer archivos

os.ReadFile (Go 1.16+)

La forma mas simple de leer un archivo completo en memoria.

package main

import (
    "fmt"
    "os"
)

func main() {
    datos, err := os.ReadFile("config.txt")
    if err != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", err)
        os.Exit(1)
    }
    fmt.Println(string(datos))
}

os.Open con io.ReadAll

Para cuando necesitas el *os.File (por ejemplo para obtener metadata).

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    archivo, err := os.Open("datos.txt") // Solo lectura
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer archivo.Close() // Siempre cerrar con defer

    contenido, err := io.ReadAll(archivo)
    if err != nil {
        fmt.Println("Error leyendo:", err)
        return
    }
    fmt.Println(string(contenido))
}

Leer linea por linea con bufio.Scanner

Ideal para archivos grandes donde no quieres cargar todo en memoria.

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    archivo, err := os.Open("log.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer archivo.Close()

    scanner := bufio.NewScanner(archivo)
    linea := 0
    for scanner.Scan() {
        linea++
        fmt.Printf("%d: %s\n", linea, scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Error de lectura:", err)
    }
}

bufio.Reader para lectura con buffer

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    archivo, err := os.Open("datos.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer archivo.Close()

    reader := bufio.NewReader(archivo)

    // Leer hasta un delimitador
    linea, err := reader.ReadString('\n')
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Print("Primera linea:", linea)
}

Escribir archivos

os.WriteFile (Go 1.16+)

package main

import "os"

func main() {
    contenido := []byte("Hola desde Go\nSegunda linea\n")
    // 0644 = owner lee/escribe, grupo y otros solo leen
    err := os.WriteFile("salida.txt", contenido, 0644)
    if err != nil {
        panic(err)
    }
}

os.Create para escritura manual

package main

import (
    "fmt"
    "os"
)

func main() {
    archivo, err := os.Create("reporte.txt") // Crea o trunca
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer archivo.Close()

    fmt.Fprintln(archivo, "Reporte de ventas")
    fmt.Fprintf(archivo, "Total: $%d\n", 15000)
    archivo.WriteString("Fin del reporte\n")
}

Abrir con flags especificos

package main

import (
    "fmt"
    "os"
)

func main() {
    // Abrir para append, crear si no existe
    archivo, err := os.OpenFile("log.txt",
        os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer archivo.Close()

    fmt.Fprintln(archivo, "Nueva entrada de log")
}
FlagDescripcion
os.O_RDONLYSolo lectura
os.O_WRONLYSolo escritura
os.O_RDWRLectura y escritura
os.O_CREATECrear si no existe
os.O_APPENDAgregar al final
os.O_TRUNCTruncar al abrir

io.Copy

Copia datos de un Reader a un Writer de forma eficiente.

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    origen, err := os.Open("original.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer origen.Close()

    destino, err := os.Create("copia.txt")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer destino.Close()

    bytes, err := io.Copy(destino, origen)
    if err != nil {
        fmt.Println("Error copiando:", err)
        return
    }
    fmt.Printf("Copiados %d bytes\n", bytes)
}

bytes.Buffer

Buffer en memoria que implementa tanto Reader como Writer.

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buf bytes.Buffer

    // Escribir
    buf.WriteString("Hola ")
    buf.WriteString("Mundo")
    fmt.Fprintf(&buf, " %d", 2024)

    // Leer
    fmt.Println(buf.String()) // Hola Mundo 2024
    fmt.Println(buf.Len())    // 15
}

filepath: rutas multiplataforma

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // Construir rutas de forma segura
    ruta := filepath.Join("home", "user", "docs", "archivo.txt")
    fmt.Println(ruta) // home/user/docs/archivo.txt

    // Extraer partes
    fmt.Println(filepath.Dir(ruta))  // home/user/docs
    fmt.Println(filepath.Base(ruta)) // archivo.txt
    fmt.Println(filepath.Ext(ruta))  // .txt

    // Ruta absoluta
    abs, _ := filepath.Abs(".")
    fmt.Println("CWD:", abs)
}

filepath.Walk y WalkDir

package main

import (
    "fmt"
    "io/fs"
    "path/filepath"
)

func main() {
    // WalkDir es mas eficiente que Walk (Go 1.16+)
    err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            return err
        }
        if d.IsDir() {
            fmt.Println("[DIR]", path)
        } else {
            info, _ := d.Info()
            fmt.Printf("  %s (%d bytes)\n", path, info.Size())
        }
        return nil
    })
    if err != nil {
        fmt.Println("Error:", err)
    }
}

Argumentos de linea de comandos

os.Args

package main

import (
    "fmt"
    "os"
)

func main() {
    // os.Args[0] es el nombre del programa
    fmt.Println("Programa:", os.Args[0])

    if len(os.Args) < 2 {
        fmt.Println("Uso: programa <nombre>")
        os.Exit(1)
    }
    fmt.Println("Hola,", os.Args[1])
}

Package flag

package main

import (
    "flag"
    "fmt"
)

func main() {
    nombre := flag.String("nombre", "mundo", "nombre a saludar")
    veces := flag.Int("veces", 1, "repeticiones del saludo")
    verbose := flag.Bool("v", false, "modo verbose")
    flag.Parse()

    for i := 0; i < *veces; i++ {
        if *verbose {
            fmt.Printf("[%d] ", i+1)
        }
        fmt.Printf("Hola, %s!\n", *nombre)
    }

    // Argumentos restantes (no-flag)
    fmt.Println("Args extra:", flag.Args())
}
go run main.go -nombre=Go -veces=3 -v extra1 extra2
# [1] Hola, Go!
# [2] Hola, Go!
# [3] Hola, Go!
# Args extra: [extra1 extra2]

Variables de entorno

package main

import (
    "fmt"
    "os"
)

func main() {
    // Leer variable
    home := os.Getenv("HOME")
    fmt.Println("HOME:", home)

    // Leer con valor por defecto
    puerto := os.Getenv("PORT")
    if puerto == "" {
        puerto = "8080"
    }

    // LookupEnv distingue vacio de no definido
    val, existe := os.LookupEnv("MI_VAR")
    if !existe {
        fmt.Println("MI_VAR no esta definida")
    } else {
        fmt.Println("MI_VAR:", val)
    }

    // Establecer variable (para el proceso actual)
    os.Setenv("APP_MODE", "development")
}

Operaciones de directorio

package main

import (
    "fmt"
    "os"
)

func main() {
    // Crear directorio
    os.Mkdir("datos", 0755)

    // Crear directorios anidados (como mkdir -p)
    os.MkdirAll("datos/2024/enero", 0755)

    // Eliminar archivo o directorio vacio
    os.Remove("archivo.tmp")

    // Eliminar directorio con contenido (como rm -rf)
    os.RemoveAll("datos")

    // Renombrar/mover
    os.Rename("viejo.txt", "nuevo.txt")

    // Directorio temporal
    dir, err := os.MkdirTemp("", "miapp-*")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer os.RemoveAll(dir)
    fmt.Println("Dir temporal:", dir)
}

← Cap 18: Testing en Go | Cap 20: JSON y HTTP →