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.
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)
}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!