본문 바로가기
빌드노트 검색

빌드노트

test -e는 실패하는데 ln은 File exists라고 한다

test -e가 거짓이어도 끊어진 심볼릭 링크는 이름을 차지한다. -L과 readlink를 함께 보고, 저장된 대상 경로가 틀렸는지 파일만 누락됐는지를 구분한다.

Linux Bash에서 test -e ./broken은 거짓인데 ls -ld ./broken에는 항목이 보이고, 같은 이름으로 링크를 만들면 File exists가 나올 수 있다. 심볼릭 링크는 남아 있지만 그 링크가 가리키는 파일은 없을 때 생기는 조합이다. -e의 실패를 그 이름이 비었다는 뜻으로 읽으면 안 된다.

Bash 조건식 문서 (새 탭에서 열림)에 따르면 파일 조건식은 별도 설명이 없는 한 심볼릭 링크를 따라 대상을 검사한다. test -e는 대상의 존재를, test -L은 검사한 경로 자체가 심볼릭 링크인지를 확인한다. 이 둘을 나눠 보면 상반된 결과를 설명할 수 있다.

같은 경로에 세 가지를 묻는다

Linux의 Bash와 GNU coreutils가 있는 환경에서 다음 진단을 실행한다. p는 문제가 난 링크의 실제 경로로 바꾸고, 이름 끝에 /는 붙이지 않는다. 거짓인 검사 뒤에도 출력을 이어 봐야 하므로 set -e가 꺼진 Bash에서 실행한다. 각 $?는 바로 앞 명령의 종료 상태다.

p='./broken'
test -e "$p"; printf 'test -e: %s\n' "$?"
test -L "$p"; printf 'test -L: %s\n' "$?"
readlink -- "$p"; printf 'readlink: %s\n' "$?"

없는 missing.txt를 가리키는 broken을 새 전용 폴더에 만들어 확인했을 때 다음처럼 나왔다. 실제 장애 기록이 아니라 합성 파일로 비교한 결과다.

test -e: 1
test -L: 0
missing.txt
readlink: 0

test의 0은 참, 1은 거짓이다. 위 결과는 대상 존재 검사는 실패했지만 링크 자체는 확인됐다는 뜻이다. readlink는 그 링크에 저장된 missing.txt라는 문자열을 읽었다. readlink의 성공은 대상 파일의 존재를 보장하지 않는다. GNU readlink 문서 (새 탭에서 열림)도 기본 모드를 링크 값 출력으로 설명한다. 여기서는 경로를 따라가는 -f나 -e 옵션을 쓰지 않았다.

같은 폴더에서 정상 링크·일반 파일·없는 경로도 비교했다. 정상 링크 good은 -e와 -L이 모두 0이었고, readlink는 regular.txt를 출력하며 0으로 끝났다. 일반 파일 regular.txt는 -e가 0, -L이 1이었으며 readlink는 출력 없이 1로 끝났다.

만들지 않은 경로 absent는 -e와 -L이 모두 1이었고, readlink도 출력 없이 1이었다. 끊어진 링크 broken과 없는 경로 absent는 -e로는 같고 -L에서 갈린다. 일반 파일과 없는 경로에서 readlink가 실패하는 동작도 문서 설명과 일치했다.

ls -ld -- ./broken은 broken -> missing.txt를 표시했다. 이때 보이는 것은 남아 있는 링크 항목이다. 그 상태에서 ln -s regular.txt broken을 실행하자 ln: failed to create symbolic link 'broken': File exists와 종료 상태 1이 나왔다. GNU ln 문서 (새 탭에서 열림)의 기본 동작은 기존 파일을 대체하지 않는다. 대상이 없더라도 broken이라는 이름은 이미 사용 중이므로 생성이 거절된 것이다.

저장된 경로가 틀렸는지, 파일만 빠졌는지 확인한다

다음 판단은 readlink가 보여 준 대상이 원래 있어야 할 파일인지다. missing.txt처럼 상대경로라면 링크가 놓인 폴더를 기준으로 찾는다. 셸의 현재 폴더를 기준으로 해석하지 않는다. 이 규칙은 GNU Coreutils FAQ의 상대 링크 설명 (새 탭에서 열림)에서도 확인할 수 있다.

앞 진단의 p를 유지한 채, 링크가 있는 폴더에서 저장된 대상 경로를 조회한다. 아래 ./missing.txt는 이번 출력에 맞춘 입력이다. 자신의 링크를 조사할 때는 readlink가 출력한 경로로 바꾸고, 절대경로가 나왔다면 그 절대경로를 사용한다. 괄호 안에서만 폴더를 바꾸므로 바깥 셸의 위치는 유지된다.

(
  cd -- "$(dirname -- "$p")" || exit 1
  ls -ld -- ./missing.txt
)

이번 실험에서는 대상 파일을 만들지 않았으므로 다음 오류와 종료 상태 2를 확인했다.

ls: cannot access './missing.txt': No such file or directory

저장된 경로가 배포 설정과 맞는데 파일만 빠졌다면 누락된 파일의 배치를 복원할 문제다. 경로 자체가 잘못됐다면 원하는 버전과 내용을 가진 파일의 위치를 확인한 뒤 링크 설정을 변경할 문제다. 이름이 비슷한 파일을 찾았다는 이유만으로 연결 대상을 정하지 않는다. -e 실패만으로는 이 두 경우를 고를 수 없다.

비교 실험에서는 원하는 내용을 sample-data로 정하고 regular.txt가 그 내용을 담았는지 확인했다. 그 파일을 사용하지 않던 새 이름 absent에 연결하자 생성이 성공했고, 링크를 통해서도 sample-data를 읽었다. 기존 broken은 여전히 missing.txt를 가리켰다. 이 결과는 확인한 후보를 새 이름에서 읽은 관찰이며 기존 링크를 복구한 결과는 아니다. 기존 링크를 삭제하거나 강제로 덮어쓰는 실험은 하지 않았다.

대상 조회가 권한 거부나 링크 순환 오류로 실패한다면 그 오류부터 조사한다. -e=1, -L=0이라는 조합만으로 대상이 삭제됐다고 단정할 수 없다. 조회 사이에 다른 프로세스가 경로를 바꿀 수도 있으므로 이 진단이 후속 생성·변경까지 보장하지도 않는다.

실행 환경은 WSL2의 Ubuntu 24.04.4, Bash 5.2.21, GNU coreutils 9.4였고 Windows 드라이브를 연결한 새 폴더에서 비교했다. 영문 오류는 LC_ALL=C에서의 출력이며 언어 설정에 따라 문구가 달라질 수 있다. 권한 거부·링크 순환·동시 변경·네트워크 파일시스템·디렉터리 대상과 끝의 /·Windows 네이티브 링크는 실행하지 않았다.

← 전체 글 보기