一覧OS-09-1 · ファイルシステム

パス・ハードリンク・シンボリックリンク

同じファイルを指す複数の名前を作り、内容変更と名前の削除が参照へ与える影響を確かめます。

説明

Unix系のファイルシステムでは、ディレクトリの名前からファイルの実体を参照します。ハードリンクは同じ実体に別の名前を付け、シンボリックリンクは参照先のパス文字列を保持します。見た目が似ていても、参照先の名前を消したときの動作は異なります。

一時ディレクトリに4バイトのファイルを作り、ハードリンクと相対パスのシンボリックリンクを追加します。実体の一致とリンク数を確認した後、元の名前を削除してそれぞれから読めるかを調べます。

1ディレクトリとファイルを作る

docsとlinksの二つのディレクトリを作ります。docs/report.txtへDATAの4バイトを保存し、内容を読みます。

import os,tempfile; from pathlib import PathファイルAPI; temp = tempfile.TemporaryDirectory(); root = Path(temp.name)作業用の領域; (root/'docs').mkdir(); (root/'links').mkdir()二つのディレクトリ; source = root/'docs/report.txt'ファイルの絶対パス; source.write_bytes(b'DATA')4バイト保存; print(source.read_bytes())内容を読む

先頭の4は保存したバイト数、続くb'DATA'は読んだ内容です。sourceは今回作った一時領域の絶対パスを保持しています。

2同じ実体へ別の名前を付ける

report.txtと同じ実体を指すhard.txtを作ります。識別にはデバイス番号とinode番号の組を使います。

hard = root/'hard.txt'追加の名前; os.link(source,hard)ハードリンクを作る; a,b = source.stat(),hard.stat()実体の属性; print((a.st_dev,a.st_ino)==(b.st_dev,b.st_ino))識別子の組を比較; print(a.st_nlink,b.st_nlink)名前の数

実体の一致はTrue、リンク数は両方2です。inode番号はファイルシステム内での識別子なので、別のファイルシステムとの比較ではデバイスも必要です。ハードリンクは通常、同じファイルシステムの範囲で作ります。

3パスを保持するリンクを作る

links/shortへ、../docs/report.txtという相対パスを保持するシンボリックリンクを作ります。readlinkは中に保存されたパス文字列を読みます。

sym = root/'links/short'リンクの場所; sym.symlink_to('../docs/report.txt')相対パスを保存; print(os.readlink(sym))リンク内の文字列; print(sym.read_bytes())参照先を読む

リンクの内容は../docs/report.txtで、たどった先からDATAが読めます。この相対パスはリンクが置かれたlinksディレクトリを基準に解決されます。呼出した側の現在ディレクトリが基準になるわけではありません。

4リンクそのものと参照先の属性

statはシンボリックリンクの参照先を調べ、lstatはリンクそのものを調べます。種類とinodeの関係を比べます。

import stat種類の判定; print(stat.S_ISLNK(sym.lstat().st_mode))リンク自身の種類; print(sym.stat().st_ino==source.stat().st_ino)参照先の一致; print(sym.lstat().st_ino==source.stat().st_ino)リンク自身との違い

リンク自身の種類はTrue、参照先のinodeは一致、リンク自身のinodeは不一致です。ハードリンクには独立したリンクファイルがあるわけではなく、複数の名前が同じinodeを指します。

5別名から内容を書き換える

hard.txtへNEXTを書き、元の名前とシンボリックリンクの両方から読みます。

hard.write_bytes(b'NEXT')別名から4バイト保存; print(source.read_bytes(),sym.read_bytes())他の参照から確認

両方NEXTです。ハードリンクはファイル内容のコピーではないため、別名からの変更も同じ実体へ届きます。書込み時の4は保存したバイト数です。

6元の名前を削除する

docs/report.txtという名前だけを削除します。hard.txtがあるため、実体にはまだリンクが一つ残ります。

source.unlink()一つの名前を削除; print(source.exists(),hard.exists())名前の存在; print(hard.stat().st_nlink,hard.read_bytes())残るリンクと内容; print(sym.exists(),sym.is_symlink())切れたシンボリックリンク

元の名前はなく、hard.txtはリンク数1でNEXTを読めます。symはリンク自身として存在しますが、参照先のパスがなくなったためexistsはFalseです。existsはこの場合リンクをたどった先の存在を判定しています。

7切れたリンクから読む

同じsymから内容を読もうとして、参照先の名前が見つからないエラーを確認します。

try:リンクをたどる print(sym.read_bytes())参照先の読取り except FileNotFoundError:パスが見つからない print('target path is missing')原因を表示

ファイルが見つからないと判定されます。hard.txtという別名が残っていても、symの中に保存された../docs/report.txtを自動的にhard.txtへ付け替える処理は行われません。

8リンク自身を削除する

シンボリックリンクを削除し、hard.txtの内容が残ることを確かめます。その後、今回の一時領域を片付けます。

sym.unlink()リンク自身を削除; print(hard.read_bytes(),hard.stat().st_nlink)実体が残ることを確認; temp.cleanup()一時領域を片付ける

NEXTとリンク数1は変わりません。シンボリックリンクを削除する操作は、そのパス文字列を持つリンク自身を消すもので、参照先のファイルを削除する操作ではありません。

まとめ

ハードリンクは同じ実体への別名であり、内容変更も共有します。

シンボリックリンクはパスを保持するため、そのパスがなくなると参照先をたどれなくなります。

statとlstatを使い分けると、リンクそのものと参照先の属性を分けて確認できます。

操作履歴と解説を振り返れます。

Python 3

1
import os,tempfile; from pathlib import Path; temp = tempfile.TemporaryDirectory(); root = Path(temp.name); (root/'docs').mkdir(); (root/'links').mkdir(); source = root/'docs/report.txt'; source.write_bytes(b'DATA'); print(source.read_bytes())
4
b'DATA'
2
hard = root/'hard.txt'; os.link(source,hard); a,b = source.stat(),hard.stat(); print((a.st_dev,a.st_ino)==(b.st_dev,b.st_ino)); print(a.st_nlink,b.st_nlink)
True
2 2
3
sym = root/'links/short'; sym.symlink_to('../docs/report.txt'); print(os.readlink(sym)); print(sym.read_bytes())
../docs/report.txt
b'DATA'
4
import stat; print(stat.S_ISLNK(sym.lstat().st_mode)); print(sym.stat().st_ino==source.stat().st_ino); print(sym.lstat().st_ino==source.stat().st_ino)
True
True
False
5
hard.write_bytes(b'NEXT'); print(source.read_bytes(),sym.read_bytes())
4
b'NEXT' b'NEXT'
6
source.unlink(); print(source.exists(),hard.exists()); print(hard.stat().st_nlink,hard.read_bytes()); print(sym.exists(),sym.is_symlink())
False True
1 b'NEXT'
False True
7
try: print(sym.read_bytes()) except FileNotFoundError: print('target path is missing')
target path is missing
8
sym.unlink(); print(hard.read_bytes(),hard.stat().st_nlink); temp.cleanup()
b'NEXT' 1

演習完了