できない.dev

ディレクトリ内のファイル一覧を取得するには

ディレクトリ直下のファイル名を取り出す基本形を各言語で示す。
サブディレクトリを除く判定と、返る値が名前かパスかの違い、並び順が保証されない点まで扱う。

公開:

各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。

Python 実行確認済み

from pathlib import Path
 
for p in sorted(Path("src").iterdir()):
    if p.is_file():
        print(p.name)

Path.iterdir() はディレクトリ直下の項目を Path として順に返す。
ディレクトリも混ざるので is_file() で絞り、名前だけ欲しいときは .name を取る。
返る順序は OS 任せなので、表示や比較に使うなら sorted() で明示的に並べる。

JavaScript 実行確認済み

import { readdir } from "node:fs/promises";
 
const entries = await readdir("src", { withFileTypes: true });
const files = entries.filter((e) => e.isFile()).map((e) => e.name);
console.log(files.sort());

readdir は既定で名前の配列を返すが、withFileTypes を付けると Dirent が返り、追加の stat 呼び出しなしでファイルとディレクトリを区別できる。
項目数が多いディレクトリでは往復が減るぶん速い。

TypeScript 実行確認済み

import { readdir } from "node:fs/promises";
import type { Dirent } from "node:fs";
 
const entries: Dirent[] = await readdir("src", { withFileTypes: true });
const files: string[] = entries.filter((e) => e.isFile()).map((e) => e.name);
console.log(files.sort());

withFileTypes の有無で readdir の戻り値の型が string[] と Dirent[] に分かれる。
型注釈を書いておくと、オプションを外したときに filter の中でコンパイルエラーになり、実行前に取り違えに気づける。

Go 静的確認

package main
 
import (
	"fmt"
	"os"
)
 
func main() {
	entries, err := os.ReadDir("src")
	if err != nil {
		panic(err)
	}
	for _, e := range entries {
		if !e.IsDir() {
			fmt.Println(e.Name())
		}
	}
}

os.ReadDir は DirEntry のスライスをファイル名順に並べて返すため、追加のソートは要らない。
DirEntry は IsDir() を持っており、判定のために別途 Stat を呼ぶ必要がない。

Rust 静的確認

use std::fs;
 
fn main() -> std::io::Result<()> {
    let mut names: Vec<String> = fs::read_dir("src")?
        .filter_map(|e| e.ok())
        .filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false))
        .map(|e| e.file_name().to_string_lossy().into_owned())
        .collect();
    names.sort();
    for n in names {
        println!("{n}");
    }
    Ok(())
}

fs::read_dir はイテレータを返し、各要素が Result になる。
項目ごとに失敗しうるので filter_map で取りこぼしを落としてから判定する。
file_name() は OsString なので、表示するときに to_string_lossy() で文字列へ移す。

Java 実行確認済み

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
 
public class ListFiles {
    public static void main(String[] args) throws IOException {
        try (Stream<Path> s = Files.list(Path.of("src"))) {
            s.filter(Files::isRegularFile)
             .map(p -> p.getFileName().toString())
             .sorted()
             .forEach(System.out::println);
        }
    }
}

Files.list が返す Stream はディレクトリを開いたままにするので、try-with-resources で閉じる。
閉じ忘れるとファイルハンドルが残り、多数のディレクトリを走査したときに枯渇する。

C# 実行確認済み

using System;
using System.IO;
using System.Linq;
 
var files = Directory.EnumerateFiles("src")
    .Select(Path.GetFileName)
    .OrderBy(name => name);
 
foreach (var name in files)
{
    Console.WriteLine(name);
}

EnumerateFiles はファイルだけを返すため、ディレクトリを除く判定は要らない。
GetFiles と違って結果を配列に溜めずに列挙するので、項目数が多いディレクトリでも先頭から処理を始められる。

ファイルとディレクトリの区別

ディレクトリの中身を列挙する API は、多くの言語でファイルとサブディレクトリを区別せずに返す。
そのまま開こうとすると、サブディレクトリに当たった時点で失敗する。
Node.js の withFileTypes や Go の DirEntry のように、列挙の結果自体が種別を持っている API を選ぶと、項目ごとに追加の問い合わせをしなくて済む。
C# の EnumerateFiles のように最初からファイルだけを返す API もある。

並び順とパスの形

返る順序は言語と OS で異なる。
Go は名前順に並べて返すが、Python の iterdir() や Node.js の readdir はファイルシステムが返した順のままである。
表示や差分比較に使うなら自分で並べ替える。
もう一つの違いは返る値の形で、名前だけを返すものと、親ディレクトリを含むパスを返すものがある。
C# の EnumerateFiles は src/a.txt のようなパスを返すため、名前だけが欲しければ Path.GetFileName を挟む。

つまずき

再帰的に辿りたい場合に、直下だけを返す API を使い続けて自分でスタックを組む必要はない。
Python なら Path.rglob、Node.js なら readdir の recursive オプション、Go なら filepath.WalkDir のように、再帰用の入口が別に用意されている。
直下の列挙と再帰の走査は別の関数だと覚えておくと、深い階層で取りこぼす事故を避けられる。

この記事は役立ちましたか?