빌드노트
다른 폴더에 복사하자 끊어진 상대 심볼릭 링크
상대 심볼릭 링크의 대상 경로는 링크가 놓인 폴더에서 해석한다. CWD를 바꿔 읽기, 링크만 복사해 실패하기, 새 위치에 맞는 상대경로로 복구하기를 합성 파일 실습으로 비교한다.
상대 심볼릭 링크를 다른 폴더에 복사하면 원본 파일이 그대로 있어도 읽지 못할 수 있다. 링크에 저장된 상대경로는 링크가 놓인 디렉터리에서 해석한다. ln -s를 실행했던 폴더를 기억해 두었다가 그곳에서 대상을 찾는 방식이 아니다.
ln -s 대상문자열 링크경로에서 두 경로의 역할을 구분하면 실수를 줄일 수 있다. 상대경로로 쓴 링크경로는 현재 작업 디렉터리에서 찾지만, 대상문자열은 링크 안에 저장한다. Linux의 symlink(2) 매뉴얼 (새 탭에서 열림)은 링크가 대상 문자열을 담으며, 그 안의 ..는 링크가 있는 디렉터리의 상위 디렉터리를 뜻한다고 설명한다.
현재 폴더를 바꿔도 같은 링크는 읽힌다
실제 장애 사례 대신 새 임시 폴더와 합성 파일로 비교했다. 실행 환경은 WSL2의 Ubuntu 24.04.4, Bash 5.2.21, GNU coreutils 9.4였다. 아래 명령은 Linux의 Bash와 GNU 도구가 필요하다. 쓰기 가능한 폴더의 새 Bash 셸에서 블록을 순서대로 실행한다. mktemp로 만든 폴더 안에서만 파일과 링크를 만든다.
export LC_ALL=C
lab=$(mktemp -d ./symlink-lab.XXXXXX) || exit 1
cd "$lab" || exit 1
mkdir -p a b/deep
printf 'sample-data\n' > a/data.txt
ln -s data.txt a/current
readlink a/current
cat a/current
(cd b && cat ../a/current)
출력은 다음과 같았다.
data.txt
sample-data
sample-data
a/current에 저장된 값은 data.txt다. 실습 폴더 바로 아래에는 data.txt가 없는데도 링크 생성과 읽기가 모두 성공했다. 링크가 놓인 a에서 찾는 a/data.txt가 있기 때문이다. 괄호 안 명령은 b로 들어가 같은 링크를 읽는다. 이때도 sample-data가 나왔고, 괄호 밖 셸은 원래 실습 폴더에 남는다.
여기서 현재 작업 디렉터리(CWD)가 쓰이는 단계는 링크 자체를 찾을 때다. b에서는 ../a/current로 접근해야 같은 링크에 도달한다. 그 링크를 만난 뒤 저장된 data.txt를 해석하는 기준은 여전히 a다.
생성할 때도 같다. 같은 실습 폴더에서 ln -s a/data.txt a/from-cwd로 만들면 저장되는 문자열은 a/data.txt다. 이 링크는 a/a/data.txt를 찾으므로 끊어진다. 별도 대조 실행에서도 링크 생성은 성공했지만 cat a/from-cwd는 파일이 없다는 오류와 종료 코드 1을 냈다. 링크 생성 성공은 대상 파일을 읽을 수 있다는 확인이 아니다.
링크만 복사하면 같은 문자열이 다른 위치를 가리킨다
앞 실습 폴더에서 다음을 실행한다. GNU cp의 -P는 원본 링크를 따라 대상 파일을 복사하는 대신 링크 자체를 복사한다. 이 옵션의 의미는 GNU coreutils의 cp(1) 매뉴얼 (새 탭에서 열림)에서도 확인할 수 있다.
cp -P a/current b/deep/current
readlink b/deep/current
cat b/deep/current
printf 'exit=%s\n' "$?"
이번 출력은 달랐다. 실패한 cat 바로 뒤에서 종료 상태를 읽었다.
data.txt
cat: b/deep/current: No such file or directory
exit=1
두 링크에 저장된 문자열은 똑같이 data.txt였다. 하지만 원본 a/current는 a/data.txt를, 복사본 b/deep/current는 b/deep/data.txt를 찾는다. 실습에서는 뒤쪽 파일을 만들지 않았으므로 복사본만 읽기에 실패했다. CWD를 바꾼 앞 실험과 달리 이번에는 링크가 놓인 디렉터리가 달라졌다.
옵션 없는 readlink는 링크에 저장된 값을 보여 준다. 값이 출력됐다고 대상의 존재까지 확인한 것은 아니다. 위에서 끊어진 링크도 data.txt를 출력했다. readlink(1) 매뉴얼 (새 탭에서 열림)의 링크 값 출력과 경로를 따라가는 옵션을 구분해서 읽어야 한다.
새 위치에서 대상까지 가는 경로로 만든다
b/deep에 링크를 두고 기존 a/data.txt를 읽으려면, b/deep에서 두 단계 위로 올라간 뒤 a/data.txt로 내려가야 한다. 새 이름 fixed로 링크를 만들어 확인한다.
ln -s ../../a/data.txt b/deep/fixed
readlink b/deep/fixed
cat b/deep/fixed
../../a/data.txt
sample-data
여기서 ../..는 명령을 실행한 셸에서 두 단계 올라가라는 뜻이 아니다. 링크가 있는 b/deep에서 출발하므로 b, 실습 폴더를 차례로 거친다. 그곳의 a/data.txt를 찾아 읽기가 성공했다.
배포하면서 링크와 대상을 함께 옮긴다면 둘 사이의 상대 배치를 유지한다. 보조 실험에서는 a와 b를 함께 새 디렉터리 아래에 복사했고, 정상인 a/current와 b/deep/fixed는 그 복사본에서도 모두 sample-data를 읽었다. 링크만 다른 깊이의 폴더에 놓을 계획이라면, 최종 링크 위치에서 대상까지 갈 상대경로를 다시 정해야 한다.
자기 링크를 확인할 때도 먼저 readlink로 저장된 문자열을 보고, 그 링크가 있는 폴더에서 그 경로를 따라가면 원하는 파일인가를 확인한다. readlink부터 실패하면 검사한 경로가 실제 심볼릭 링크인지 확인한다. 대상 경로가 맞아도 읽기에 실패한다면 파일의 존재와 접근 권한 등 다른 조건을 살펴야 한다.
이번 실행은 WSL2에서 Windows 드라이브를 연결한 실습 폴더의 일반 디렉터리와 파일을 대상으로 했다. 중간 경로가 또 다른 링크인 배치, 권한 거부, 네트워크 파일시스템과 Windows 네이티브 링크는 비교하지 않았다. 이 실험이 확인한 것은 CWD 변경, 링크만 복사, 상대경로 수정, 링크와 대상의 배치를 유지한 복사 사이의 차이다.