Saltar al contenido

Leer y modificar archivos y carpetas en Go

Recetas mínimas para leer un archivo, sobreescribirlo y recorrer una carpeta de forma recursiva en Go, con un ejemplo práctico que combina las tres.

2 min de lectura
  • go
  • snippets

Este artículo se escribió en 2021. Desde Go 1.16, los helpers viven en os en lugar de io/ioutil (os.ReadFile, os.WriteFile); el paquete io/ioutil quedó deprecado pero sigue funcionando. Los snippets de abajo ya están actualizados a la API moderna.

Hace unas semanas necesité recorrer miles de archivos y carpetas, leer su contenido y contar cuántos cumplían con cierta condición. Mi primer instinto fue hacerlo con Node, pero después de revisar los paquetes equivalentes en Go terminé eligiendo Go: la API es más directa y el binario resultante se distribuye sin runtime.

Estos son los snippets que terminé reusando.

Leer el contenido de un archivo

package main
 
import (
	"fmt"
	"os"
)
 
const filePath = "file.txt"
 
func main() {
	content, err := os.ReadFile(filePath)
	if err != nil {
		panic(err)
	}
 
	fmt.Println("File content:", string(content))
}

Crear o sobreescribir un archivo

os.WriteFile crea el archivo si no existe y lo trunca si ya existía.

package main
 
import (
	"os"
)
 
const filePath = "./file.txt"
 
func main() {
	newContent := []byte("file was modified")
 
	// 0644 = rw para el dueño, r para grupo y resto.
	// Calculadora útil: https://chmod-calculator.com
	if err := os.WriteFile(filePath, newContent, 0644); err != nil {
		panic(err)
	}
}

Recorrer una carpeta de forma recursiva

filepath.WalkDir es la versión moderna de filepath.Walk: usa fs.DirEntry y evita un os.Stat extra por archivo.

package main
 
import (
	"fmt"
	"io/fs"
	"path/filepath"
)
 
const folderPath = "/my/folder/path"
 
func main() {
	err := filepath.WalkDir(folderPath, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		fmt.Printf("found: %s\n", d.Name())
		return nil
	})
	if err != nil {
		panic(err)
	}
}

Un ejemplo práctico

Combinemos los tres helpers para resolver lo siguiente:

Recorrer una carpeta con múltiples archivos y subcarpetas. Por cada archivo:

  1. Si su extensión es .ts o .tsx y la primera línea es // @deprecated, sumarlo al contador de archivos deprecados.
  2. Si su extensión es .py, anteponer # file modified como primera línea.

Regla extra: ignorar las carpetas llamadas tmp.

Al final imprimir el resumen.

package main
 
import (
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
)
 
const folderPath = "/my/folder/path"
 
func main() {
	var modifiedFiles, deprecatedFiles int
 
	err := filepath.WalkDir(folderPath, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
 
		// Ignorar carpetas llamadas "tmp"
		if d.IsDir() && d.Name() == "tmp" {
			return filepath.SkipDir
		}
 
		if d.IsDir() {
			return nil
		}
 
		switch filepath.Ext(d.Name()) {
		case ".py":
			content, err := os.ReadFile(path)
			if err != nil {
				return err
			}
			newContent := append([]byte("# file modified\n"), content...)
			if err := os.WriteFile(path, newContent, 0644); err != nil {
				return err
			}
			modifiedFiles++
 
		case ".ts", ".tsx":
			content, err := os.ReadFile(path)
			if err != nil {
				return err
			}
			firstLine, _, _ := strings.Cut(string(content), "\n")
			if strings.TrimSpace(firstLine) == "// @deprecated" {
				deprecatedFiles++
			}
		}
 
		return nil
	})
	if err != nil {
		panic(err)
	}
 
	fmt.Println("Total files with extension .py modified:    ", modifiedFiles)
	fmt.Println("Total files with the \"@deprecated\" comment:", deprecatedFiles)
}

Cierre

La librería estándar de Go cubre la mayoría de operaciones de filesystem sin necesidad de dependencias extra, y la API se siente coherente una vez que conoces os, io/fs y path/filepath. Para tareas como esta — scripts puntuales que tocan miles de archivos — terminó siendo una solución mucho más predecible que su equivalente en Node.

¡Gracias por leer!