ディレクトリ内のファイル一覧を取得するには
ディレクトリ直下のファイル名を取り出す基本形を各言語で示す。
サブディレクトリを除く判定と、返る値が名前かパスかの違い、並び順が保証されない点まで扱う。
公開:
各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 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 のように、再帰用の入口が別に用意されている。
直下の列挙と再帰の走査は別の関数だと覚えておくと、深い階層で取りこぼす事故を避けられる。