パス・ハードリンク・シンボリックリンク
同じファイルを指す複数の名前を作り、内容変更と名前の削除が参照へ与える影響を確かめます。
説明
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
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'
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
sym = root/'links/short'; sym.symlink_to('../docs/report.txt'); print(os.readlink(sym)); print(sym.read_bytes())../docs/report.txt b'DATA'
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
hard.write_bytes(b'NEXT'); print(source.read_bytes(),sym.read_bytes())4 b'NEXT' b'NEXT'
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
try:
print(sym.read_bytes())
except FileNotFoundError:
print('target path is missing')target path is missing
sym.unlink(); print(hard.read_bytes(),hard.stat().st_nlink); temp.cleanup()b'NEXT' 1
演習完了