빌드노트
하드링크로 보관한 파일까지 같이 바뀌는 이유
하드링크는 원본과 같은 inode를 공유하므로 truncate·write의 영향을 함께 받는다. device·inode·링크 수와 실제 내용을 비교하고, 새 파일로 이름을 교체했을 때 이전 하드링크가 남는 차이를 확인한다.
ln original.txt hard.txt로 보관본을 만들었는데 원본을 덮어쓴 뒤 보관본도 바뀌었다면, 두 이름이 같은 파일을 가리키는지 확인해야 한다. 하드링크는 내용을 복사하지 않고 같은 inode에 이름을 하나 더 붙인다. 기존 파일에 쓰면 두 이름에서 변경이 보이고, 새 파일로 이름을 교체하면 다른 하드링크에는 이전 파일이 남는다.
먼저 Linux의 GNU stat으로 조사할 두 파일을 조회한다. 아래 경로만 실제 경로로 바꾸면 되며, 파일을 수정하는 명령은 아니다.
stat -c '%n type=%F dev=%d inode=%i links=%h' -- original.txt hard.txt
두 항목이 일반 파일이고 dev와 inode가 모두 같으면 현재 같은 파일이다. inode 번호는 파일시스템 안에서만 고유하므로 번호 하나만 비교하지 않는다. links는 그 파일의 하드링크 수이며, 둘 다 2라고 해서 서로 연결됐다는 뜻은 아니다. inode(7) (새 탭에서 열림)
type=symbolic link라면 심볼릭 링크 자체를 조회한 것이다. stat -L은 대상을 따라가므로, 그 결과가 같다는 이유로 하드링크라고 판단하면 안 된다. %d·%i·%h·%F와 -L의 의미는 GNU stat 매뉴얼 (새 탭에서 열림)에 나와 있다. 일반 파일의 device·inode 쌍이 다르면 현재 별도 파일이며, 조회 오류가 나면 먼저 경로와 접근 권한을 확인한다.
내용을 독립적으로 보관하려면 원본을 수정하기 전에 아직 없는 새 이름에 일반 cp로 복사하고 device·inode 쌍이 원본과 다른지 확인한다. 같은 내용이나 크기만으로는 독립 복사본인지 알 수 없다. 이미 바뀐 하드링크가 변경 전 내용을 따로 간직하지도 않는다. link(2) (새 탭에서 열림)는 두 이름이 같은 파일을 가리키며 어느 쪽이 원래 이름인지 구별할 수 없다고 설명한다.
비우기와 쓰기는 같은 파일에 닿는다
다음 입력은 새 임시 폴더에서 하드링크와 일반 복사본의 차이를 확인한다. Linux Bash와 GNU coreutils가 필요하며, Bash 기본값처럼 noclobber가 꺼진 상태를 전제로 한다. 명령이 실패하면 멈추고, 뒤의 비우기·교체 명령을 기존 사용자 파일에 적용하지 않는다. ln의 기본 동작은 하드링크 생성이며 ln -s는 심볼릭 링크 생성이다. GNU ln 매뉴얼 (새 탭에서 열림)
work=$(mktemp -d) && cd -- "$work" || exit 1
printf 'original-v1\n' > original.txt
ln -- original.txt hard.txt
cp -- original.txt copy.txt
stat -c '%n dev=%d inode=%i links=%h' -- original.txt hard.txt copy.txt
: > original.txt
wc -c original.txt hard.txt copy.txt
printf 'changed\n' > original.txt
paste original.txt hard.txt copy.txt
실제 실행에서 첫 stat은 원본·하드링크의 device와 inode가 같고 links=2임을 보여 줬다. 복사본은 같은 device에 다른 inode, links=1이었다. : > original.txt 직후 크기는 원본·하드링크·복사본 순으로 0·0·12바이트였고, 마지막 paste의 세 열은 changed·changed·original-v1이었다. 12바이트에는 줄바꿈이 포함된다.
:가 내용을 출력하지 않아도 >는 기존 파일을 열면서 길이를 0으로 줄인다. Bash 출력 리다이렉션 설명 (새 탭에서 열림)에 따른 동작이다. 단계별 추가 조회에서도 원본·하드링크의 inode와 링크 수 2는 유지됐다. 다른 이름으로 변경이 복사된 것이 아니라 같은 파일을 두 이름으로 읽은 결과다. 복사본까지 바뀐다면 먼저 첫 조회에서 정말 다른 device·inode 쌍이었는지 확인한다.
이름을 교체하면 하드링크는 이전 파일에 남는다
같은 실습 폴더에서 아직 없는 replacement.txt를 만든 뒤 원본 이름으로 옮긴다. 이 입력은 앞에서 만든 합성 파일만 교체하며, 기존 파일에 직접 쓴 경우와 비교하기 위한 것이다.
printf 'replacement\n' > replacement.txt
mv -T -- replacement.txt original.txt
stat -c '%n dev=%d inode=%i links=%h' -- original.txt hard.txt copy.txt
paste original.txt hard.txt copy.txt
이번 paste는 replacement·changed·original-v1을 출력했다. original.txt는 새 파일의 inode로 바뀌고, hard.txt는 이전 inode와 changed 내용을 유지했다. 이전 inode의 링크 수는 2에서 1로 줄었고 새 파일도 links=1이었다. 일반 복사본은 계속 별도 파일이었다. rename(2) (새 탭에서 열림)는 이름을 바꾸거나 기존 이름을 교체해도 다른 하드링크는 영향을 받지 않는다고 설명한다. 여기서는 같은 폴더에서 교체했으며, 다른 파일시스템으로 옮길 때의 복사 동작까지 뜻하지 않는다.
별도 대조에서 original.txt를 가리키는 심볼릭 링크는 교체 뒤 새 replacement를 읽었다. 심볼릭 링크는 경로를 따라가고, 하드링크는 연결된 이전 파일에 남는 차이다. 따라서 편집기의 ‘저장’이 보관본까지 바꾸는지도 저장 방식에 달려 있다. 해당 편집기를 별도 실습 파일에 적용해 저장 전후의 device·inode와 내용을 비교해야 하며, 이 실험으로 특정 편집기의 저장 방식을 확인한 것은 아니다.
실행 환경은 WSL2 Ubuntu 24.04.4, Bash 5.2.21, GNU coreutils 9.4였고, 실습 위치는 Windows 드라이브를 연결한 v9fs였다. 하드링크에는 파일시스템 지원과 권한이 필요하며 서로 다른 마운트 사이에는 만들 수 없다. 실패했다면 생성됐다고 가정하지 말고 오류와 조회 결과부터 확인한다. link(2)의 오류 조건 (새 탭에서 열림)
직접 확인한 범위는 새 소유 일반 파일의 truncate·write와 같은 폴더의 이름 교체다. ext4·NFS에서의 별도 대조, 서비스가 열린 파일을 계속 쓰는 경우의 로그 재열기와 로그 회전 전체, 동시 쓰기·전원 장애 후 보존은 검증하지 않았다. 순차적인 stat 조회도 다른 프로세스의 경로 교체를 막아 주지는 않는다.