Skip to content

ディレクトリを再帰的に走査する snippet

ルート配下のすべてのエントリを深さ優先で列挙します。繰り返し現れる2つの落とし穴があります。1つは、再帰がシンボリックリンクを stat すると、祖先ディレクトリへのリンクをたどって終了しなくなることです。修正策は lstat 相当のチェックか、デフォルトでたどらない API です。もう1つは、読み取り不能な1つのサブディレクトリが、API 次第で走査全体を中断したり、黙って飲み込まれたりすることです。実際に望むエラーの扱いを選んでください。イテレーションの順序は OS 任せです。出力を決定的にする必要がある場合は、コールバック内でソートしてください。SQL は省略します。エンジンは行を独自のカタログで管理するのであって、ディスク上のファイルではありません。Postgres の pg_ls_dir はサーバープロセス内で動くスーパーユーザー関数であり、ポータブルなクライアント側の走査ではありません。

ルート配下のすべてのエントリを深さ優先で列挙します。繰り返し現れる2つの落とし穴があります。1つは、再帰がシンボリックリンクを stat すると、祖先ディレクトリへのリンクをたどって終了しなくなることです。修正策は lstat 相当のチェックか、デフォルトでたどらない API です。もう1つは、読み取り不能な1つのサブディレクトリが、API 次第で走査全体を中断したり、黙って飲み込まれたりすることです。実際に望むエラーの扱いを選んでください。イテレーションの順序は OS 任せです。出力を決定的にする必要がある場合は、コールバック内でソートしてください。SQL は省略します。エンジンは行を独自のカタログで管理するのであって、ディスク上のファイルではありません。Postgres の pg_ls_dir はサーバープロセス内で動くスーパーユーザー関数であり、ポータブルなクライアント側の走査ではありません。

Runnable recipe · 14 languages
Files & Datafilesystemrecursiontraversal

Every language

14 languages, 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.