cp는 데이터를 복제하고, mv는 파일 데이터는 그대로 둔 채 디렉터리 엔트리(이름·위치 정보)만 갱신하며, rm은 파일을 직접 지우는 대신 링크 카운트를 낮춰 0이 되면 데이터 블록을 해제한다. 겉보기엔 셋 다 “파일을 다루는 명령"이지만, 실제로는 파일시스템을 서로 다른 층위에서 조작한다.
이 장을 읽기 전에
선행 챕터: 이 장은 06장: mkdir, rmdir에서 디렉터리를 만들고 지우는 법을 다룬 뒤 이어진다. 디렉터리를 만들 수 있어야 그 안팎으로 파일을 복사·이동할 대상 경로를 준비할 수 있으므로, 이 장은 06장에서 만든 디렉터리 구조를 전제로 예시를 구성한다. 03장: cat에서 파일 내용을 확인하는 법을 알면 복사·이동 결과를 검증하기 더 수월하다.
이 장의 깊이: 입문–중급 난이도다. 기본 복사·이동·삭제 옵션부터 재귀 처리, 속성 보존, 파괴적 동작에 대한 방어까지 실무에서 매일 쓰는 범위를 다룬다. 다루지 않는 것: 데이터를 물리적으로 복제하지 않고 참조만 추가하는 하드링크·심볼릭 링크는 08장: ln에서 다루고, 파일 자체의 소유자·권한 비트를 바꾸는 방법은 36장: chmod, chown에서 다룬다. 삭제된 파일을 복구하는 포렌식 기법도 이 장의 범위 밖이다.
당신의 수준에 맞는 경로
| 수준 | 읽을 부분 | 핵심 목표 |
|---|---|---|
| 파일 정리가 급한 사람 | “사용법·옵션” ~ “예시"의 기본 예시 | cp/mv/rm의 기본 옵션 조합으로 파일·디렉터리를 바로 복사·이동·삭제한다 |
| 스크립트에 안전하게 쓰려는 사람 | “개요 + 정신 모델"부터 “주의사항·함정”, “흔한 오개념” 전체 | rm -rf의 위험성과 mv의 파일시스템 경계 함정, 공백 파일명 처리까지 이해하고 스크립트에 안전하게 적용한다 |
개요 + 정신 모델
세 명령 모두 “파일을 다룬다"는 점은 같지만, 실제로 손대는 대상은 서로 다른 층위에 있다. 유닉스 파일시스템에서 파일의 실제 내용(데이터 블록)과 그 내용을 가리키는 inode, 그리고 사람이 보는 “파일 이름"은 서로 분리된 개념이다. 디렉터리는 “이름 → inode 번호” 매핑표(디렉터리 엔트리)를 담은 특수한 파일에 불과하다.
cp는 원본 데이터를 처음부터 끝까지 읽어 새 inode와 새 데이터 블록에 통째로 다시 쓴다. 그래서 원본을 지워도 복사본은 전혀 영향받지 않고, 대신 원본 크기만큼 디스크 공간과 I/O 시간이 그대로 든다. mv는 반대로 데이터 블록에는 손도 대지 않는다 — 같은 파일시스템 안에서 이동한다면 그저 디렉터리 엔트리의 이름과 부모 디렉터리 정보만 갱신할 뿐이라, 파일 크기와 무관하게 사실상 즉시 끝난다. rm은 파일을 “지운다"기보다 그 이름이 가리키던 inode의 링크 카운트를 하나 줄인다 — 카운트가 0이 되고 그 inode를 참조하는 열린 파일 디스크립터도 없어야 커널이 비로소 데이터 블록을 회수한다. 하드링크가 여러 개 걸린 파일에서 rm을 한 번 실행해도 다른 이름이 남아 있으면 데이터는 그대로 살아 있는 이유가 여기에 있다.
사용법 · 옵션
cp — 복사
| |
소스가 여러 개면 대상은 반드시 기존 디렉터리여야 한다. 소스가 하나뿐이고 대상이 디렉터리가 아니면 그 이름으로 새 파일을 만든다.
| 옵션 | 설명 |
|---|---|
-r, -R, --recursive | 디렉터리를 재귀적으로 복사 |
-i, --interactive | 덮어쓰기 전 확인 |
-f, --force | 열 수 없는 대상 파일은 지우고 다시 시도(덮어쓰기 강제) |
-p | 권한·타임스탬프·소유자 등 속성 유지 |
-a, --archive | 아카이브 모드. -dR --preserve=all과 동등 — 심볼릭 링크를 링크 그대로 유지하고 권한·타임스탬프까지 보존 |
-u, --update | 대상이 없거나 원본보다 오래된 경우에만 복사 |
-v, --verbose | 복사되는 파일마다 출력 |
mv — 이동·이름 변경
| |
같은 파일시스템 안에서는 디렉터리 엔트리만 갱신되어 사실상 즉시 처리되지만, 다른 파일시스템으로 이동할 때는 커널이 내부적으로 복사 후 원본 삭제로 전환한다(자세한 내용은 “주의사항·함정” 참고).
| 옵션 | 설명 |
|---|---|
-i, --interactive | 덮어쓰기 전 확인 |
-f, --force | 확인 없이 덮어쓰기 강제 |
-n, --no-clobber | 기존 파일을 덮어쓰지 않음 |
-u, --update | 대상이 없거나 원본보다 오래된 경우에만 이동 |
-v, --verbose | 이동되는 파일마다 출력 |
rm — 삭제
| |
휴지통 개념 없이 링크 카운트를 곧바로 낮추므로, 복구를 전제로 사용해서는 안 된다.
| 옵션 | 설명 |
|---|---|
-r, -R, --recursive | 디렉터리를 재귀적으로 삭제 |
-i, --interactive | 항목마다 삭제 전 확인 |
-I | 3개 초과 항목 삭제 또는 재귀 삭제 시 한 번만 확인(GNU 확장) |
-f, --force | 존재하지 않는 파일을 무시하고, 확인 없이 강제 삭제 |
-d, --dir | 빈 디렉터리 삭제 허용(재귀 없이) |
-v, --verbose | 삭제되는 파일마다 출력 |
예시
cp — 복사
| |
mv — 이동·이름 변경
| |
rm — 삭제
| |
주의사항·함정
rm -rf의 비가역성: rm은 휴지통을 거치지 않는다. -r(재귀)과 -f(확인 없이 강제)를 함께 쓰면 지정한 경로 전체가 확인 절차 하나 없이 즉시 삭제되고, 실행 취소 명령도 없다. 잘못된 경로(특히 변수가 빈 문자열로 확장된 rm -rf "$DIR"/*처럼)를 넘기면 의도치 않은 전체 삭제로 이어질 수 있어, 실무에서 가장 위험한 명령 중 하나로 꼽힌다. 스크립트에서 rm -rf를 쓸 때는 삭제 대상 변수가 비어 있지 않은지 ${DIR:?}처럼 방어적으로 검사하고, 가능하면 실행 전 echo나 -i로 대상을 먼저 확인하는 습관을 들이는 편이 안전하다.
mv가 파일시스템 경계를 넘으면 느려진다: “개요 + 정신 모델"에서 설명했듯 mv가 빠른 이유는 데이터를 손대지 않고 디렉터리 엔트리만 갱신하기 때문이다. 하지만 원본과 대상이 서로 다른 마운트 지점(예: /와 별도로 마운트된 외장 드라이브, 다른 파티션)에 있으면 디렉터리 엔트리만 바꾸는 방식이 불가능하다 — inode 번호 자체가 파일시스템마다 독립적인 번호 체계이기 때문이다. 이 경우 커널은 내부적으로 cp(데이터 전체 복사) 후 원본을 rm하는 방식으로 전환한다. 대용량 파일을 다른 파일시스템으로 옮기는 스크립트가 예상보다 오래 걸린다면 이 전환이 원인일 가능성이 높다.
cp -r과 cp -a는 다르다: -r은 디렉터리를 재귀적으로 복사할 뿐, 권한·소유자·타임스탬프·심볼릭 링크를 원본 그대로 유지한다는 보장이 없다(구현에 따라 심볼릭 링크를 링크가 아니라 그 대상 파일 내용으로 따라가 복사하기도 한다). -a(아카이브 모드)는 -dR --preserve=all과 동등해, 심볼릭 링크는 링크 그대로 두고 권한·타임스탬프·소유자까지 원본과 동일하게 복제한다. 백업이나 배포처럼 원본 속성을 그대로 보존해야 하는 작업에는 -r이 아니라 -a를 써야 한다.
와일드카드 확장과 공백 파일명: rm *.txt는 셸이 먼저 *.txt를 현재 디렉터리의 실제 파일 목록으로 확장한 뒤 그 목록을 rm에 넘긴다. 매칭되는 파일이 하나도 없을 때 nullglob 셸 옵션이 꺼져 있으면(기본값) 패턴 문자열 *.txt가 그대로 인자로 전달돼 “그런 파일 없음” 오류가 나거나, 공교롭게 *.txt라는 이름의 파일이 실제로 있으면 그 파일이 지워진다. 파일명에 공백이 포함된 경우도 함정이다 — rm my file.txt는 my와 file.txt라는 별개의 두 인자로 쪼개져 전달되므로, 공백이 든 파일명은 항상 따옴표로 감싸거나(rm "my file.txt") 대량 처리 시 find ... -print0 | xargs -0처럼 널 문자로 구분하는 안전한 조합을 써야 한다.
이식성: cp -a는 GNU coreutils의 확장 옵션이며, macOS 등 BSD 계열 cp도 -a를 지원하지만 --preserve=all 같은 GNU 전용 롱옵션 문법은 없다. rm -I(3개 초과 삭제 시 한 번만 확인)도 GNU 확장으로 BSD rm에는 없다. 여러 배포판·OS를 오가며 스크립트를 쓴다면 롱옵션보다 -r, -i, -f, -a 같은 짧은 옵션 위주로 맞추는 편이 안전하다.
흔한 오개념
"rm은 파일을 즉시 지운다”는 정확하지 않다. 앞서 정신 모델에서 설명했듯 rm은 이름과 inode 사이의 연결(링크)을 하나 끊을 뿐이며, 같은 inode를 가리키는 다른 하드링크가 남아 있거나 어떤 프로세스가 그 파일을 이미 열어 둔 상태라면 데이터는 계속 디스크에 남아 있다. 로그 파일을 열어 둔 프로세스가 있는 상태에서 그 로그 파일을 rm해도 디스크 공간이 바로 줄지 않는 현상(df와 du 결과가 어긋나는 대표적 원인)이 바로 이 때문이다 — 그 프로세스가 파일을 닫아야 비로소 공간이 회수된다.
"cp -r과 cp -a는 같은 뜻이다”도 흔한 오해다. 둘 다 디렉터리를 재귀적으로 다룬다는 점은 같지만, 위 “주의사항·함정"에서 설명했듯 속성 보존 범위가 다르다. 배포·백업 스크립트에서 이 둘을 섞어 쓰면 권한이나 심볼릭 링크가 원본과 다르게 복사되는 결과로 이어질 수 있다.
다음 장에서는
지금까지는 파일을 복제하거나 옮겼다면, 다음은 08장: ln이다 — 파일을 물리적으로 복제하지 않고 여러 이름으로 참조하게 만드는 링크(하드링크·심볼릭 링크)를 다룬다.
평가 기준
cp·mv·rm이 각각 데이터 블록·디렉터리 엔트리·링크 카운트 중 무엇을 조작하는지 설명할 수 있다.cp -r과cp -a의 차이(권한·타임스탬프·심볼릭 링크 보존 여부)를 설명하고 상황에 맞게 선택할 수 있다.mv가 파일시스템 경계를 넘을 때 내부적으로 어떤 동작으로 전환되는지, 왜 느려지는지 설명할 수 있다.rm -rf가 왜 비가역적인지 설명하고, 스크립트에서 이를 안전하게 방어하는 방법(변수 검사,-i, 사전 확인)을 적용할 수 있다.- 공백이 포함된 파일명을 다룰 때 와일드카드·따옴표·
find -print0/xargs -0중 상황에 맞는 방법을 선택할 수 있다.
![Featured image of post [Bash Shell] 07. cp, mv, rm - 파일 복사·이동·삭제](/post/bashshell/cp-mv-rm-commands-copy-move-delete-files/wordcloud_hu_3ba086147d61af39.webp)
![[Bash Shell] 05. touch - 파일 생성과 타임스탬프 갱신](/post/bashshell/touch-command-create-file-update-timestamp/wordcloud_hu_7ec28326489f97a8.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] 40. du, df - 디스크 사용량 확인](/post/bashshell/du-df-commands-disk-usage-linux/wordcloud_hu_13758518c299ef5e.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)