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.
- go
- snippets
Este artículo se escribió en 2021. Desde Go 1.16, los helpers viven en
osen lugar deio/ioutil(os.ReadFile,os.WriteFile); el paqueteio/ioutilquedó 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:
- Si su extensión es
.tso.tsxy la primera línea es// @deprecated, sumarlo al contador de archivos deprecados. - Si su extensión es
.py, anteponer# file modifiedcomo 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)
}Por qué Go en vez de Node para esto
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!