Skip to content

Recorre un directorio de forma recursiva snippet

Enumera todas las entradas bajo una raíz, en profundidad.

Enumera todas las entradas bajo una raíz, en profundidad. Dos trampas se repiten: la recursión que hace stat de los symlinks seguirá un enlace de vuelta a un directorio ancestro y no terminará nunca — la solución son comprobaciones estilo lstat o APIs que por defecto no siguen enlaces — y un único subdirectorio ilegible aborta el recorrido entero o se traga en silencio según la API, así que elige el sabor de error que de verdad quieres. El orden de iteración lo decide el SO: ordena dentro del callback cuando la salida debe ser determinista. SQL se omite: un motor gestiona las filas en su propio catálogo, no en ficheros del disco — el pg_ls_dir de Postgres es una función de superusuario que corre dentro del proceso del servidor, no un recorrido portable desde el cliente.

Receta ejecutable · 14 lenguajes
Files & Datafilesystemrecursiontraversal

Every language

14 lenguajes, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
import { readdir } from 'node:fs/promises';

async function walk(dir) {
  const entries = await readdir(dir, { withFileTypes: true });
  for (const entry of entries) {
    const path = `${dir}/${entry.name}`;
    console.log(path);
    if (entry.isDirectory()) await walk(path);
  }
}

await walk('src');

withFileTypes yields Dirents whose isDirectory() reflects the entry itself, so symlinked directories are never descended and cycles are impossible. Node 20+ can pass recursive: true to readdir and skip the recursion entirely.

TSTypeScript
import { readdir } from 'node:fs/promises';

async function walk(dir: string): Promise<void> {
  const entries = await readdir(dir, { withFileTypes: true });
  for (const entry of entries) {
    const path = `${dir}/${entry.name}`;
    console.log(path);
    if (entry.isDirectory()) await walk(path);
  }
}

await walk('src');

Identical runtime to the JavaScript version — readdir's overloads infer Dirent[] from withFileTypes, so no cast is needed.

GoGo
package main

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

func main() {
	err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err // return nil here to log-and-skip instead
		}
		fmt.Println(path)
		return nil
	})
	if err != nil {
		panic(err)
	}
}

WalkDir reads each directory once and never follows symlinks — they are yielded as entries but not descended. The err argument to the callback is how an unreadable subtree surfaces; returning nil from that branch skips it and keeps walking. The callback also fires for the root itself.

RsRust
use walkdir::WalkDir;

fn main() {
    for entry in WalkDir::new("src").into_iter().filter_map(|e| e.ok()) {
        println!("{}", entry.path().display());
    }
}

walkdir = "2" in Cargo.toml — the de-facto crate. filter_map(|e| e.ok()) turns a permission-denied entry into a skip instead of aborting the walk; symlinks are yielded but never descended unless you opt in with follow_links(true), which detects ancestor loops for you. Stdlib-only alternative: fs::read_dir plus manual recursion on entry.file_type()?.is_dir().

PHPPHP
<?php
$it = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('.', FilesystemIterator::SKIP_DOTS),
    RecursiveIteratorIterator::SELF_FIRST
);
foreach ($it as $file) {
    echo $file->getPathname(), PHP_EOL;
}

The default LEAVES_ONLY mode yields plain files and silently skips the directories themselves — SELF_FIRST is what makes it an actual tree walk. SKIP_DOTS suppresses . and .., and symlinked directories are not descended unless you pass FilesystemIterator::FOLLOW_SYMLINK (cyclic links then loop forever).

PyPython
import os

for root, dirs, files in os.walk("src"):
    dirs.sort()  # OS order is arbitrary — pin it for reproducible output
    print(root)
    for name in files:
        print(os.path.join(root, name))

os.walk is top-down by default, so mutating dirs in place prunes subtrees: dirs[:] = [d for d in dirs if d != 'node_modules']. followlinks=False is the default — passing True invites infinite recursion on cyclic symlinks. pathlib.Path('src').rglob('*') is the flat one-liner when you don't need pruning.

CC
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

static void walk(const char *dir) {
    DIR *d = opendir(dir);
    if (!d) {
        perror(dir);
        return;
    }
    struct dirent *ent;
    while ((ent = readdir(d)) != NULL) {
        if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
            continue;
        char path[4096];
        snprintf(path, sizeof path, "%s/%s", dir, ent->d_name);
        printf("%s\n", path);
        struct stat st;
        if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
            walk(path);
    }
    closedir(d);
}

int main(void) {
    walk(".");
    return 0;
}

lstat, not stat: a symlink to a directory must be printed but never descended, or a link back to an ancestor recurses until the stack dies. Skip . and .. or the walk never ends. d_type in struct dirent is a non-portable optimization — portable code stats every entry. POSIX only; Windows needs FindFirstFileW.

C++C++
#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

int main() {
    for (const auto &entry : fs::recursive_directory_iterator(".")) {
        std::cout << entry.path() << '\n';
    }
}

C++17 — the iterator owns an internal stack and does the recursion for you. Directory symlinks are not followed by default; pass fs::directory_options::follow_directory_symlink to opt in. Call it.disable_recursion_pending() when you see a subtree you want to skip.

C#C#
using System;
using System.IO;

class Program
{
    static void Main()
    {
        var options = new EnumerationOptions
        {
            RecurseSubdirectories = true,
            IgnoreInaccessible = true,
            AttributesToSkip = FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReparsePoint,
        };

        foreach (var entry in Directory.EnumerateFileSystemEntries(".", "*", options))
        {
            Console.WriteLine(entry);
        }
    }
}

EnumerationOptions needs .NET Core 2.1+. IgnoreInaccessible skips permission-denied entries instead of throwing UnauthorizedAccessException mid-walk, and ReparsePoint in AttributesToSkip stops the walk descending into symlinked or junctioned directories — the default skip set does not include it.

JvJava
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

public class Walk {
    public static void main(String[] args) throws IOException {
        try (Stream<Path> stream = Files.walk(Path.of("src"))) {
            stream.forEach(System.out::println);
        }
    }
}

Files.walk is lazy and backed by open directory handles — the try-with-resources is mandatory or they leak. It never follows symlinks unless you pass FileVisitOption.FOLLOW_LINKS (which detects cycles). For tree operations like copy or delete, walkFileTree with a SimpleFileVisitor is the variant with per-error callbacks; Path.of needs Java 11+.

SwSwift
import Foundation

let fm = FileManager.default
let root = URL(fileURLWithPath: "src")

let enumerator = fm.enumerator(
    at: root,
    includingPropertiesForKeys: [.isDirectoryKey],
    errorHandler: { url, error in
        print("skipped \(url.path): \(error.localizedDescription)")
        return true // keep walking past unreadable entries
    }
)!

while let url = enumerator.nextObject() as? URL {
    print(url.path)
}

NSDirectoryEnumerator is pre-order: each directory is yielded before its contents, and the order among siblings is unspecified. Returning true from errorHandler keeps the walk going after a failed entry; returning false stops it. The prefetch keys are an optimization — read isDirectory off url.resourceValues when you need to branch.

KtKotlin
import java.io.File

fun main() {
    File("src").walkTopDown()
        .onEnter { it.name != "node_modules" }
        .forEach(::println)
}

kotlin.io's FileTreeWalk is a lazy Sequence, so filter/take don't slurp the tree. walkTopDown yields each directory before its contents, walkBottomUp after — the order you want for deletes. onEnter returning false prunes a subtree. Caveat: unlike most walkers here it descends into symlinked directories (File.isDirectory follows links), so a cyclic link yields a deep copy of the tree instead of a skip — filter !it.isSymbolicLink in onEnter when that matters.

RbRuby
require 'find'

Find.find('src') do |path|
  puts path
end

stdlib 'find'. The block receives the root itself, then every entry depth-first; prune subtrees with `next Find.prune` (e.g. for node_modules). Ruby 3.0+ ignores unreadable directories by default — pass ignore_error: false to make Find.find raise instead.

ZigZig
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var walker = try std.fs.walk.walk(std.heap.page_allocator, .{
        .root_path = ".",
        .yield_directories = true,
    });
    defer walker.deinit();

    while (try walker.next()) |entry| {
        try stdout.print("{s}\n", .{entry.path});
    }
}

Stdlib only (Zig 0.11+). yield_directories defaults to false — the walker yields files only unless you flip it. It never descends into symlinked directories, walker.next() returns null at the end of the tree, and deinit() frees the arena the paths live in.