ln은 파일을 복제하지 않고 새로운 이름으로 참조하게 만드는 명령이다. 기본으로 만드는 하드링크는 원본과 완전히 동등한 이름표일 뿐이다 — 같은 inode를 가리키는 디렉터리 엔트리가 하나 더 생길 뿐이라, “원본"과 “복사본"이라는 구분 자체가 성립하지 않는다. -s 옵션으로 만드는 심볼릭 링크는 정반대로, 대상 경로를 문자열로 담은 완전히 별개의 작은 파일이라 원본이 사라지면 깨진 링크(dangling link)로 남는다.
이 장을 읽기 전에
선행 챕터: 이 장은 07장: cp, mv, rm에서 파일을 물리적으로 복제(cp)하거나 옮기는(mv) 법을 다룬 뒤 이어진다. 지금까지는 파일을 물리적으로 복제하거나 옮겼다면, 이번 장은 파일을 복제하지 않고 여러 이름으로 참조하는 법을 다룬다 — 07장에서 배운 “데이터 블록·디렉터리 엔트리·링크 카운트” 구분을 그대로 전제로 예시를 구성하므로, 07장의 “개요 + 정신 모델"을 먼저 읽어 두면 이해가 빠르다.
이 장의 깊이: 입문–중급 난이도다. 하드링크·심볼릭 링크를 만들고 구분하는 기본 사용법부터, 상대·절대 경로 타겟의 차이, 파일시스템 경계 제약까지 실무에서 실제로 마주치는 범위를 다룬다. 다루지 않는 것: 링크 대상 파일 자체의 소유자·권한 비트를 바꾸는 법은 36장: chmod, chown에서 다루고, 명령어를 셸이 어떤 경로에서 찾아내는지(심볼릭 링크로 설치된 실행 파일 포함)는 09장: which, whereis, locate에서 이어서 다룬다. 파일시스템 내부의 inode 자료구조 자체(디스크 블록 배치, 저널링 등)는 이 장의 범위 밖이다.
당신의 수준에 맞는 경로
| 수준 | 읽을 부분 | 핵심 목표 |
|---|---|---|
| 링크를 처음 만들어 보는 사람 | “사용법·옵션"부터 “예시"까지의 기본 예시 | 하드링크·심볼릭 링크를 각각 만들고 ls -li로 구분할 수 있다 |
| 스크립트·배포에 안전하게 쓰려는 사람 | “개요 + 정신 모델"부터 “주의사항·함정”, “흔한 오개념” 전체 | ln -s의 인자 순서 실수를 피하고, 상대·절대 경로 타겟과 파일시스템 경계 제약을 이해해 배포 스크립트에 안전하게 적용한다 |
개요 + 정신 모델
07장에서 정리했듯, 유닉스 파일시스템에서 파일의 실제 내용(데이터 블록)과 그 내용을 가리키는 inode, 그리고 사람이 보는 “파일 이름"은 서로 분리된 개념이다. 디렉터리는 “이름 → inode 번호” 매핑표에 지나지 않으며, 하나의 inode를 가리키는 이름(디렉터리 엔트리)은 원래부터 여러 개일 수 있다. ln은 바로 이 매핑표에 새 항목을 추가하는 명령이다.
하드링크를 만들면 기존 파일이 가리키던 것과 똑같은 inode 번호를 가리키는 디렉터리 엔트리가 하나 더 생긴다. 두 이름은 완전히 동등한 자격을 갖는다 — 어느 쪽이 “원본"이고 어느 쪽이 “링크"인지 파일시스템은 구분하지 않으며, 둘 중 하나를 수정하면 같은 데이터 블록을 가리키므로 다른 이름으로 열어도 수정된 내용이 그대로 보인다. inode에는 그 inode를 가리키는 이름의 개수를 세는 링크 카운트가 있어, rm으로 이름 하나를 지워도 카운트가 0이 될 때까지는 데이터가 삭제되지 않는다(이 부분은 07장에서 다룬 rm의 동작과 정확히 같은 메커니즘이다).
심볼릭 링크는 전혀 다른 층위에서 동작한다. ln -s로 만든 심볼릭 링크는 자기 자신의 inode와 데이터 블록을 따로 갖는 완전히 독립된 파일이며, 그 데이터 블록 안에는 대상의 경로 문자열이 텍스트로 저장돼 있을 뿐이다. 셸이나 커널이 이 파일을 열면 저장된 경로 문자열을 다시 해석(resolve)해 실제 대상을 찾아간다. 그래서 심볼릭 링크는 원본과 inode 번호가 다르고, 원본이 삭제·이동되면 그 문자열이 더 이상 유효한 경로를 가리키지 못해 “깨진 링크(dangling link)“가 된다 — 하드링크에서는 원천적으로 불가능한 상태다.
사용법 · 옵션
| |
첫 번째 형태가 가장 흔히 쓰인다 — TARGET(연결할 대상)과 LINK_NAME(새로 만들 이름)을 순서대로 받는다. LINK_NAME을 생략하면(2번째 형태) 현재 디렉터리에 TARGET과 같은 이름으로 링크를 만들고, TARGET을 여러 개 넘기면(3·4번째 형태) 마지막 인자인 DIRECTORY 안에 각 TARGET과 같은 이름으로 링크를 만든다. 기본은 하드링크이며, -s를 주면 심볼릭 링크를 만든다.
| 옵션 | 설명 |
|---|---|
-s, --symbolic | 하드링크 대신 심볼릭 링크를 만든다 |
-f, --force | 기존 대상 파일을 지우고 덮어쓴다 |
-i, --interactive | 기존 대상을 지우기 전 확인 |
-n, --no-dereference | LINK_NAME이 디렉터리를 가리키는 심볼릭 링크여도 그 안이 아니라 그 이름 자체를 대상으로 취급 |
-v, --verbose | 링크가 만들어질 때마다 이름 출력 |
-r, --relative | -s와 함께 써서, LINK_NAME의 위치를 기준으로 한 상대경로 타겟을 자동 계산해 링크 생성 |
-t, --target-directory=DIRECTORY | 링크를 만들 디렉터리를 명시적으로 지정(마지막 인자 대신) |
-b, --backup[=CONTROL] | 기존 대상 파일을 덮어쓰기 전 백업 |
-d, -F, --directory | (GNU 확장, 슈퍼유저 전용) 디렉터리에 대해서도 하드링크 시도 — 대부분의 최신 파일시스템은 이 자체를 지원하지 않는다 |
-L, --logical / -P, --physical | TARGET이 심볼릭 링크일 때 그 대상을 따라갈지(-L) 심볼릭 링크 자체에 하드링크를 걸지(-P, 기본값)를 결정 |
예시
| |
주의사항·함정
ln -s target linkname 인자 순서를 헷갈리는 실수: ln -s는 cp·mv와 마찬가지로 “무엇을(target), 어디에(linkname)” 순서를 따르지만, 초심자는 종종 이를 “링크 이름을 먼저, 그다음 원본"으로 반대로 생각해 인자를 뒤집어 쓴다. ln -s symlink.txt original.txt처럼 순서를 뒤집으면 original.txt라는 이름의 심볼릭 링크가(이미 그 이름의 실제 파일이 있다면 명령 자체가 실패하거나, 없다면) symlink.txt라는 아직 존재하지도 않는 경로를 가리키는 상태로 생성된다. 더 위험한 경우는 대상 위치에 이미 파일이 있을 때다 — 인자를 뒤집은 채 -f까지 붙이면 의도와 달리 원본 파일 자리에 깨진 심볼릭 링크를 덮어써 버릴 수 있다. ln -s를 칠 때마다 “먼저 오는 인자가 이미 존재하는 실제 대상, 나중 인자가 새로 만들 이름"이라는 순서를 의식적으로 확인하는 습관이 필요하다.
상대경로 타겟과 절대경로 타겟은 동작이 다르다: ln -s에 넘기는 TARGET 문자열을 상대경로로 쓰느냐 절대경로로 쓰느냐에 따라 링크의 견고성이 크게 달라진다. 심볼릭 링크 안에 저장된 상대경로는 링크 파일 “자신이 위치한 디렉터리"를 기준으로 해석되므로, 링크가 있는 디렉터리 자체를 통째로 다른 위치로 옮기면(링크와 대상의 상대적 위치 관계가 그대로 유지되는 한) 여전히 유효하지만, 링크는 그대로 두고 대상만 옮기거나 링크만 다른 디렉터리로 옮기면 그 순간 깨진다. 절대경로 타겟은 파일시스템 어디서 링크를 열든 항상 같은 곳을 가리키므로 이런 문제가 없지만, 반대로 전체 디렉터리 트리를 통째로 다른 마운트 지점이나 컨테이너로 옮기는 배포 시나리오에서는 오히려 절대경로가 깨지고 상대경로가 살아남는다. -r(--relative) 옵션은 이 계산을 손으로 하지 않고 ln이 LINK_NAME 위치를 기준으로 올바른 상대경로 문자열을 자동으로 만들어 준다.
하드링크는 파일시스템 경계를 넘지 못한다: 하드링크는 같은 inode 번호를 가리키는 디렉터리 엔트리를 추가하는 것인데, inode 번호 체계는 파일시스템마다 독립적으로 관리된다. 그래서 서로 다른 마운트 지점(파티션, 별도 디스크, 다른 파일시스템 타입)에 걸쳐 하드링크를 만들려고 하면 커널이 이를 거부한다. Linux의 link(2) 시스템 콜 매뉴얼은 이 제약을 다음과 같이 명시한다.
“EXDEV: oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points, but link() does not work across different mounts, even if the same filesystem is mounted on both.)” — link(2), Linux manual page
반면 심볼릭 링크는 경로 문자열만 담을 뿐이라 이런 제약이 전혀 없다 — 다른 파티션, 다른 디스크, 심지어 아직 존재하지 않는 경로도 가리킬 수 있다. 여러 디스크에 걸친 디렉터리 구조를 하나로 묶어야 한다면 하드링크가 아니라 심볼릭 링크(또는 마운트 바인드)를 써야 한다.
하드링크는 디렉터리를 대상으로 만들 수 없다: GNU ln의 -d/--directory 옵션은 “슈퍼유저가 디렉터리에 대한 하드링크를 시도할 수 있게 허용"한다고 문서화돼 있지만, 이는 시스템이 실제로 지원할 때에 한한다. man7.org의 ln(1) 매뉴얼은 이 옵션을 “if supported by the system"이라는 단서와 함께 소개하며, 대부분의 현대 파일시스템은 디렉터리 순환 참조로 인한 무한 루프·fsck 붕괴 위험 때문에 루트 권한으로도 이를 막아 둔다. 디렉터리를 다른 이름으로 참조하고 싶다면 처음부터 심볼릭 링크를 써야 한다.
흔한 오개념
“하드링크로 연결된 파일 중 하나를 지우면 다른 쪽도 같이 사라진다”는 정확하지 않다. “개요 + 정신 모델"에서 설명했듯 rm은 이름과 inode 사이의 연결을 하나 끊어 링크 카운트를 줄일 뿐이다. 하드링크로 연결된 이름이 두 개 있는 상태에서 하나를 지워도, 남은 이름의 링크 카운트가 1로 줄어들 뿐 데이터는 그대로 살아 있고 남은 이름으로 정상적으로 파일에 접근할 수 있다. 두 이름 모두 지워야, 즉 링크 카운트가 0이 돼야 비로소 데이터 블록이 회수된다.
“심볼릭 링크는 원본 파일의 ‘바로가기’일 뿐이라 사실상 원본과 똑같다”도 흔한 오해다. 윈도우의 바로가기(shortcut)에 익숙한 사용자가 특히 이렇게 생각하기 쉽다. 하지만 심볼릭 링크는 자기 자신의 inode·소유자·타임스탬프를 따로 가진 독립된 파일 객체이며(단, 대부분의 명령은 심볼릭 링크를 열 때 그 대상의 권한을 따라간다), 대상이 이동·삭제되면 자기 자신은 멀쩡히 남아 있으면서도 더 이상 아무것도 가리키지 못하는 깨진 링크가 된다. 하드링크에서는 “대상이 사라진다"는 개념 자체가 존재하지 않는다는 점과 대비된다.
다음 장에서는
09장: which, whereis, locate에서는 셸이 명령어의 실행 파일을 어떤 경로에서 찾아내는지를 다룬다. 이 장에서 다룬 심볼릭 링크는 실무에서 /usr/local/bin에 실행 파일을 연결해 여러 버전을 전환하는 용도로도 흔히 쓰이는데, 그렇게 연결된 명령을 셸이 실제로 어떻게 찾아내는지가 다음 장의 주제다.
평가 기준
- 하드링크와 심볼릭 링크가 각각 파일시스템에서 무엇을 참조하는지(같은 inode 대 별도 파일에 담긴 경로 문자열) 설명할 수 있다.
ln -s의 인자 순서(대상이 먼저, 새 링크 이름이 나중)를 실수 없이 적용할 수 있다.- 상대경로 심볼릭 링크와 절대경로 심볼릭 링크가 각각 어떤 상황에서 깨지는지 설명할 수 있다.
- 하드링크가 파일시스템 경계를 넘지 못하는 이유를 inode 번호 체계 관점에서 설명할 수 있다.
- 상황(같은 파일시스템 내 참조인지, 디렉터리를 가리켜야 하는지, 파일시스템 경계를 넘는지)에 따라 하드링크와 심볼릭 링크 중 적절한 쪽을 선택할 수 있다.
![Featured image of post [Bash Shell] 08. ln - 하드링크와 심볼릭 링크](/post/bashshell/ln-command-hard-symbolic-links-linux/wordcloud_hu_19a5c72f0cc22bc3.webp)
![[Bash Shell] 06. mkdir, rmdir - 디렉터리 생성과 삭제](/post/bashshell/mkdir-rmdir-commands-create-delete-directories/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 07. cp, mv, rm - 파일 복사·이동·삭제](/post/bashshell/cp-mv-rm-commands-copy-move-delete-files/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 08. ln - 하드링크와 심볼릭 링크](/post/bashshell/ln-command-hard-symbolic-links-linux/wordcloud_hu_76b51dfeca0d4f59.webp)
![[Bash Shell] 09. which, whereis, locate - 명령어와 파일 찾기](/post/bashshell/which-whereis-locate-commands-find-command-location/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 10. PATH, type, hash, command - 셸의 명령어 탐색 원리](/post/bashshell/path-type-hash-command-shell-command-lookup/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 05. touch - 파일 생성과 타임스탬프 갱신](/post/bashshell/touch-command-create-file-update-timestamp/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 01. cd, pwd - 디렉터리 이동과 현재 위치](/post/bashshell/cd-pwd-change-directory-linux-commands/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 03. cat, head, tail - 파일 내용 출력과 일부 보기](/post/bashshell/cat-head-tail-commands-view-file-contents/wordcloud_hu_7ec28326489f97a8.webp)
![[Bash Shell] 04. less, more - 페이저로 파일 보기](/post/bashshell/less-more-commands-view-large-files-linux/wordcloud_hu_7ec28326489f97a8.webp)