Crear y listar directorios en Go

Dos paquetes se reparten el trabajo: os crea y lista, y path/filepath recorre árboles y compone rutas. Usar filepath en vez de pegar cadenas con barras es lo que hace que el mismo código funcione en Linux y en Windows.

Crear

fmt.Println(os.Mkdir("salida/informes", 0755))     // falta el padre
fmt.Println(os.MkdirAll("salida/informes", 0755))
fmt.Println(os.MkdirAll("salida/informes", 0755))  // y otra vez
salidamkdir salida/informes: no such file or directory
<nil>
<nil>

La diferencia es la de siempre: Mkdir crea una sola carpeta y exige que la que la contiene exista; MkdirAll crea todo lo que falte. Y MkdirAll no protesta si ya existe, lo que permite llamarla sin comprobar nada antes.

Listar un nivel

ent, err := os.ReadDir("salida")
if err != nil {
	return err
}
for _, e := range ent {
	tipo := "archivo"
	if e.IsDir() {
		tipo = "carpeta"
	}
	fmt.Printf("%-8s %s\n", tipo, e.Name())
}
salidacarpeta  informes
archivo  notas.txt

os.ReadDir devuelve la lista ya ordenada por nombre. Cada entrada trae el nombre y si es carpeta sin coste extra; para el tamaño o la fecha hay que pedir e.Info(), que sí consulta el disco.

Recorrer el árbol entero

err := filepath.WalkDir("salida", func(ruta string, d fs.DirEntry, err error) error {
	if err != nil {
		return err          // un error al entrar en esa ruta
	}
	if d.IsDir() {
		fmt.Println("[dir]", ruta)
		return nil
	}
	info, _ := d.Info()
	fmt.Printf("      %-32s %4d bytes\n", ruta, info.Size())
	return nil
})
salida[dir] salida
[dir] salida/informes
      salida/informes/enero.csv           4 bytes
      salida/informes/febrero.csv         4 bytes
[dir] salida/informes/viejos
      salida/informes/viejos/2024.csv     4 bytes
      salida/notas.txt                    5 bytes

La función se llama una vez por cada archivo y cada carpeta. Devolver nil continúa; devolver un error corta el recorrido entero.

Saltarse una carpeta

if d.IsDir() && d.Name() == "viejos" {
	return filepath.SkipDir
}
salidasalida/informes/enero.csv
salida/informes/febrero.csv
salida/notas.txt

filepath.SkipDir es un error especial que no es un error: le dice al recorrido «esta carpeta entera, no». Es como se ignoran los node_modules y los .git del mundo.

WalkDir y no Walk. La función antigua, filepath.Walk, pedía la información completa de cada archivo aunque no la usaras, y en árboles grandes eso se nota mucho. WalkDir solo la pide si se la pides tú con d.Info().

Filtrar por patrón

coincide, _ := filepath.Glob("salida/informes/*.csv")
for _, c := range coincide {
	fmt.Println(c)
}
salidasalida/informes/enero.csv
salida/informes/febrero.csv

Glob no entra en subcarpetas: el * no cruza barras. Para buscar en todo el árbol hay que combinarlo con WalkDir y filepath.Match.

Rutas

r := filepath.Join("salida", "informes", "enero.csv")

fmt.Println("Join ->", r)
fmt.Println("Dir  ->", filepath.Dir(r))
fmt.Println("Base ->", filepath.Base(r))
fmt.Println("Ext  ->", filepath.Ext(r))
abs, _ := filepath.Abs(r)
fmt.Println("Abs  ->", abs)
salidaJoin -> salida/informes/enero.csv
Dir  -> salida/informes
Base -> enero.csv
Ext  -> .csv
Abs  -> /home/ana/w/dirs/salida/informes/enero.csv
Nunca pegues rutas con + "/" +. filepath.Join usa el separador del sistema, limpia las barras repetidas y resuelve los ... En Windows el mismo código produce salida\informes\enero.csv sin que tengas que hacer nada. Ojo con el paquete parecido path, a secas: ese es solo para URL y usa siempre la barra inclinada.

Todo el código se compiló y ejecutó con Go 1.25 antes de publicarla; las salidas están copiadas de esa ejecución. Documentación oficial: os y path/filepath.