Python でシンボリックリンクとハードリンクを扱う

シンボリックリンクとハードリンクは、1 つのファイルに複数の名前を付ける仕組みだ。Python ではどちらも作成・操作できる。

シンボリックリンクとハードリンクの違い

シンボリックリンク

ファイルの「パス」を指すショートカット。リンク先が削除されると壊れる(dangling link)。異なるファイルシステムを跨げる。ディレクトリにもリンクできる。

ハードリンク

同じ inode を共有する別名。どちらのリンクを削除しても、もう一方からアクセスできる。同じファイルシステム内でのみ作成可能。ディレクトリには作成できない。

シンボリックリンクを作成する

os.symlink() でシンボリックリンクを作成する。

import os

# target へのシンボリックリンクを link_name として作成
os.symlink('target.txt', 'link_to_target.txt')

pathlib でも同様に作成できる。

from pathlib import Path

Path('link_to_target.txt').symlink_to('target.txt')

ディレクトリへのシンボリックリンクを作成する場合は、target_is_directory=True を指定する(Windows で必要)。

os.symlink('mydir', 'link_to_dir', target_is_directory=True)

ハードリンクを作成する

os.link() でハードリンクを作成する。

import os

os.link('original.txt', 'hardlink.txt')

pathlib では link_to() を使う(Python 3.10 以降は hardlink_to())。

from pathlib import Path

# Python 3.10 以降
Path('hardlink.txt').hardlink_to('original.txt')

シンボリックリンクかどうかを判定する

import os
from pathlib import Path

# os.path
print(os.path.islink('link_to_target.txt'))  # True

# pathlib
print(Path('link_to_target.txt').is_symlink())  # True

シンボリックリンクの参照先を取得する

import os
from pathlib import Path

# os
target = os.readlink('link_to_target.txt')
print(target)  # target.txt

# pathlib
target = Path('link_to_target.txt').readlink()
print(target)  # target.txt

シンボリックリンクを解決する

resolve() はシンボリックリンクをたどって最終的なパスを返す。

from pathlib import Path

# link -> dir/file.txt の場合
real_path = Path('link').resolve()
print(real_path)  # /home/user/dir/file.txt

os.path.realpath() も同様だ。

import os

real_path = os.path.realpath('link')

リンクをたどらずに操作する

os.lstat() はシンボリックリンク自体の情報を返す(os.stat() はリンク先の情報を返す)。

import os

# リンク先の情報
stat_info = os.stat('link_to_target.txt')

# リンク自体の情報
lstat_info = os.lstat('link_to_target.txt')

ハードリンクの数を確認する

os.stat()st_nlink でハードリンクの数がわかる。

import os

nlinks = os.stat('original.txt').st_nlink
print(f'リンク数: {nlinks}')

通常のファイルは 1、ハードリンクを作成すると 2 以上になる。

壊れたシンボリックリンクの検出

リンク先が存在しないシンボリックリンク(dangling link)を検出する。

from pathlib import Path

def is_broken_symlink(path):
    p = Path(path)
    return p.is_symlink() and not p.exists()

# 使用例
if is_broken_symlink('link_to_target.txt'):
    print('壊れたシンボリックリンクです')

exists() はリンク先の存在を確認するが、is_symlink() はリンク自体の存在を確認する。両方を組み合わせることで壊れたリンクを検出できる。