Unix ではディレクトリも「ファイル」の一種として扱われる。ただし通常のファイルとは異なり、特殊な構造を持っている。
ディレクトリは特殊なファイル
ディレクトリの実体は「ファイル名と inode 番号のペアを格納したテーブル」だ。
任意のバイト列を格納する。テキスト、画像、バイナリなど何でも入る
「ファイル名 → inode 番号」のマッピングを格納する。中身はカーネルが管理する特殊なフォーマット
# ディレクトリも inode を持つ ls -lid /home/user # 1048577 drwxr-xr-x 5 user user 4096 Jan 15 10:00 /home/user # 'd' はディレクトリを示す stat /home/user | grep "File:" # File: /home/user # Size: 4096 Blocks: 8 IO Block: 4096 directory
ディレクトリエントリの構造
ディレクトリ内の各エントリは「dirent(directory entry)」と呼ばれる構造を持つ。
エントリが指すファイルの inode 番号。
可変長の文字列。ファイルシステムによって最大長が異なる(ext4 では 255 バイト)。
通常ファイル、ディレクトリ、シンボリックリンクなどを示すフラグ(一部のファイルシステムのみ)。
Python から見ると、os.scandir() がこのディレクトリエントリに直接アクセスする。
import os
# scandir はディレクトリエントリを直接読む
with os.scandir("/home/user") as entries:
for entry in entries:
print(f"{entry.name}: inode={entry.inode()}, is_dir={entry.is_dir()}")
os.listdir() よりも os.scandir() のほうが効率的なのは、ディレクトリエントリから直接情報を取得でき、追加の stat() 呼び出しが不要な場合があるためだ。
「.」と「..」エントリ
すべてのディレクトリには 2 つの特殊エントリが存在する。
自分自身を指す。現在のディレクトリの inode への参照
親ディレクトリを指す。一つ上の階層の inode への参照
# . と .. の inode を確認 ls -lia /home/user # 1048577 drwxr-xr-x 5 user user 4096 Jan 15 10:00 . # 1048576 drwxr-xr-x 3 root root 4096 Jan 10 09:00 .. # . は /home/user の inode # .. は /home の inode
これが「ディレクトリのリンクカウントが 2 以上になる」理由だ。空のディレクトリでもリンクカウントは 2(自身へのエントリ「.」と親からのエントリ)。サブディレクトリを作ると、子の「..」から参照されるためカウントが増える。
なぜディレクトリを直接読めないのか
ディレクトリは特殊なファイルなので、通常の read() では中身を読めない。
# これはエラーになる
try:
with open("/home/user", "r") as f:
content = f.read()
except IsADirectoryError as e:
print(e) # [Errno 21] Is a directory: '/home/user'
カーネルがディレクトリの読み取りを禁止しているのは、フォーマットがファイルシステムごとに異なるためだ。代わりに readdir() システムコール(Python では os.scandir() や os.listdir())を使う。
// C 言語では opendir/readdir を使う #include <dirent.h> DIR *dir = opendir("/home/user"); struct dirent *entry; while ((entry = readdir(dir)) != NULL) { printf("%s: inode=%lu\n", entry->d_name, entry->d_ino); } closedir(dir);
ディレクトリのハードリンクは作れない
ファイルにはハードリンクを作れるが、ディレクトリには(通常)作れない。
# ディレクトリへのハードリンクは禁止 ln /home/user /tmp/userlink # ln: /home/user: hard link not allowed for directory
ディレクトリのハードリンクを許可すると、ファイルシステムが循環参照を持つ可能性があり、多くのツール(find、du など)が無限ループに陥る危険がある。「.」と「..」は例外的に許可された特殊なハードリンクだ。
ディレクトリのサイズ
ディレクトリのサイズは「中身のファイルの合計サイズ」ではなく、「ディレクトリエントリを格納する領域のサイズ」だ。
# 空のディレクトリでも 4096 バイト(1 ブロック) mkdir empty_dir ls -ld empty_dir # drwxr-xr-x 2 user user 4096 Jan 15 10:00 empty_dir # ファイルを大量に作るとディレクトリ自体のサイズが増える cd empty_dir touch file{1..10000} ls -ld . # drwxr-xr-x 2 user user 155648 Jan 15 10:01 .
ディレクトリを「中身をリストアップするための帳簿」と考えると理解しやすい。帳簿自体のページ数と、帳簿に記録された商品の合計価格は別物だ。