find는 디렉터리를 걸어 내려가며 조건에 맞는 파일을 찾고, 그 파일마다 출력하거나 명령을 실행하는 프로그램이다. 리눅스의 find는 대부분 GNU findutils다. 이 글은 `find --version`이 4.8.0인 GNU find를 기준으로 한다.
grep과 sed와 awk는 파일 내용을 다룬다. find는 이름, 종류, 크기, 시각, 권한, 소유자처럼 파일 자체의 속성을 다룬다. 내용 검색은 find로 고른 뒤에 `grep`에 넘기는 식이 맞다.
명령의 형태
find 시작경로... 식시작 경로를 생략하면 `.`부터 찾는다. 식 안에 출력이나 삭제 같은 동작이 하나도 없으면, 식이 참인 경로마다 `-print`를 한 것과 같다. `-prune`만 있는 경우도 기본 출력이 붙는다.
find .
find . -name '*.txt'첫 명령은 현재 디렉터리 아래의 모든 경로를 한 줄에 하나씩 출력한다. 둘째는 파일 이름이 `*.txt`인 것만 출력한다. 따옴표가 없으면 셸이 `*.txt`를 현재 디렉터리의 파일 이름으로 먼저 바꿔 버리므로, find의 `-name`까지 글자 그대로 전달되지 않는다.
경로는 식보다 앞에 둔다. `find -name '*.txt' .`처럼 식을 경로보다 앞에 두면 경로를 식으로 오해하거나 경고가 난다.
식은 왼쪽에서 오른쪽으로 계산된다
식은 참과 거짓을 돌려주는 검사와 동작의 나열이다. 검사를 빈칸으로 나란히 쓰면 AND다. `-a`라고 적어도 같다. AND는 OR보다 먼저 묶인다.
find . -type f -name '*.log'
find . -name '*.log' -o -name '*.txt'첫 명령은 일반 파일이면서 이름이 `*.log`인 것이다. 둘째는 이름이 `*.log`이거나 `*.txt`인 것이다. 의도와 다르게 묶이지 않게 하려면 괄호를 쓴다. 괄호는 셸의 문법이라 역슬래시나 따옴표가 필요하다.
find . \( -name '*.log' -o -name '*.txt' \) -type f`!`는 부정을 뜻한다. GNU find는 `-not`도 받는다.
find . -type d ! -name '.*'AND와 OR는 앞의 결과만으로 전체가 결정되면 뒤를 실행하지 않는다. `-name`이 거짓이면 그 뒤의 `-exec`은 실행되지 않는다. 그래서 좁은 검사를 동작보다 앞에 둔다.
쉼표 `,`도 연산자다. 양쪽을 모두 실행하고, 식의 값은 오른쪽 결과다. 자주 쓰지는 않는다.
어디를 걷고, 어디를 걷지 않는가
시작 경로 자체도 검사 대상이다. `-maxdepth 0`이면 시작 경로만 보고 그 안으로는 들어가지 않는다. `-maxdepth 1`은 시작 경로와 그 바로 아래만 본다. `-mindepth 1`은 시작 경로 자신은 빼고 그 안부터 본다. 이 두 검사는 GNU find의 기능이다.
find /var/log -maxdepth 1 -type f
find . -mindepth 1 -maxdepth 1 -type d`-xdev`는 시작 경로가 있는 파일 시스템을 벗어나지 않는다. 다른 디스크, 다른 마운트를 건너뛴다. `-mount`는 같은 뜻의 예전 이름이다.
`-depth`는 디렉터리 안을 먼저 처리하고 그 디렉터리 자신은 나중에 처리한다. 디렉터리를 비운 뒤에 디렉터리를 지울 때 필요하다. `-delete`는 `-depth`를 포함하고 있다.
디렉터리 하나를 통째로 빼고 싶으면 그 안의 파일을 일일이 검사하지 말고 `-prune`을 쓴다. `-prune`이 참이면 find는 그 디렉터리 안으로 내려가지 않는다. OR의 왼쪽에서 가지치기를 하고, 오른쪽에서 출력하는 형태가 안전하다.
find . -path './.git' -prune -o -type f -print`-path`가 맞으면 `-prune`이 되고, 그때는 OR의 오른쪽 `-print`를 하지 않는다. 그래서 `.git` 자신은 출력되지 않고, 그 안으로도 들어가지 않는다. `-print`를 빼면 기본 출력이 식 전체에 붙어 `.git`도 함께 나올 수 있다. 가지치기를 쓸 때는 출력을 오른쪽에 직접 적는 편이 분명하다.
이름만으로 빼려면 디렉터리인지 확인한다.
find . -type d -name node_modules -prune -o -type f -print이름
`-name`은 디렉터리를 뺀 파일 이름만 셸 패턴과 비교한다. 패턴의 기준은 정규식이 아니다. `*`는 아무 길이나 맞고, `?`는 한 글자, `[]`는 그 안의 한 글자다. 이 메타문자는 이름의 맨 앞 `.`에도 맞는다. 숨김 파일도 `*`에 포함된다는 뜻이다. 중괄호 `{}`는 패턴에서 특별한 의미가 없다. 셸만 중괄호를 확장한다.
슬래시가 들어 있는 `-name 'a/b'`는 아무것과도 맞지 않는다. 파일 이름에는 슬래시가 없기 때문이다. 경로 전체를 보려면 `-path`를 쓴다.
find . -name '*.jpg'
find . -path '*/2026/*.jpg'`-iname`과 `-ipath`는 영문 대소문자를 구분하지 않는다. 한글 이름에는 대소문자 구분이 없다.
`-regex`는 경로 전체를 정규식으로 맞춘다. 일부만 찾는 검색이 아니라, 경로 전체가 식과 맞아야 한다. 기본 문법은 Emacs 정규식이다. `find . -name foo`에 해당하는 정규식은 `.*foo`가 아니라 경로 전체를 설명하는 식이다.
find . -regextype posix-extended -regex '.*\.(jpg|png)'`-regextype`은 그 뒤에 나오는 `-regex`에만 적용된다. 쓸 수 있는 이름은 `find -regextype help`로 본다. 이름만 거를 때는 `-name`이 더 단순하고 빠르다.
종류
`-type`의 글자는 하나다.
- `f`는 일반 파일이다. - `d`는 디렉터리다. - `l`은 심볼릭 링크다. - `p`는 파이프, `s`는 소켓, `b`는 블록 장치, `c`는 문자 장치다.
find . -type d
find /dev -maxdepth 1 -type b기본값 `-P`에서는 심볼릭 링크를 따라가지 않는다. 링크 자체는 `-type l`이고, 링크가 가리키는 파일의 종류는 보지 않는다. `-xtype f`는 링크가 아닌 일반 파일이거나, 일반 파일을 가리키는 링크를 고를 때 쓴다. 깨진 링크를 찾으려면 링크인데 가리키는 대상의 종류 검사가 실패하는 경우를 본다.
find . -xtype l이 검사는 GNU find의 기능이고, 따라가지 않는 기본 상태에서 깨진 링크를 잡는 데 쓴다.
`-L`을 주면 검색 중에 만난 디렉터리 링크도 따라간다. 링크가 순환하면 같은 곳을 반복할 수 있다. `-H`는 명령에 적은 시작 경로만 따라가고, 검색 중에 만난 링크는 따라가지 않는다. `-H`, `-L`, `-P`를 여러 번 쓰면 마지막 것이 적용된다. 이 옵션은 경로보다 앞에 둔다.
find -P . -type l
find -H /path/link-to-dir -type f크기
`-size`의 숫자는 반올림한 단위다. 접미사가 없으면 512바이트 블록이다. 바이트로 세려면 `c`를 붙인다.
- `c`는 바이트다. - `k`는 1024바이트다. - `M`은 1024×1024바이트다. - `G`는 1024×1024×1024바이트다. - `w`는 2바이트다.
`+`는 더 큼, `-`는 더 작음이고, 그 딱 맞는 크기는 포함하지 않는다. 접미사 없이 쓰는 블록 수와 `c`를 붙인 바이트 수는 반올림 때문에 같은 경계가 아니다. 매뉴얼은 `-size -1M`이 빈 파일만 맞추고, `-size -1048576c`는 0바이트부터 1048575바이트까지 맞춘다고 적고 있다. 크기를 자를 때는 `c`로 바이트를 적는 편이 오해가 적다.
find . -type f -size 0c
find . -type f -size +100M
find . -type f -size -1048576c`-empty`는 크기가 0인 일반 파일이거나, 항목이 없는 디렉터리다. GNU 확장이다.
시각
`-mtime n`은 파일 내용이 마지막으로 바뀐 시각을 24시간 단위로 자른 값과 비교한다. 자투리 시간은 버린다. 그래서 경계가 달력의 자정이 아니라 지금으로부터 몇 번이나 24시간이 지났는지다.
- `-mtime 0`과 `-mtime -1`은 최근 24시간 안에 바뀐 파일이다. - `-mtime 1`은 24시간 전보다 오래되고 48시간보다는 최근인 파일이다. - `-mtime +1`은 적어도 이틀, 곧 48시간보다 이전에 바뀐 파일이다. 24시간을 갓 넘긴 파일은 여기 들어가지 않는다. - `-mtime +0`은 24시간보다 이전에 바뀐 파일이다.
`-atime`은 마지막 접근, `-ctime`은 inode 상태가 바뀐 시각이다. 권한이나 소유자를 바꿔도 `-ctime`은 갱신된다. 내용은 그대로일 수 있다. `-mmin`, `-amin`, `-cmin`은 같은 계산을 분 단위로 한다.
find /var/log -type f -mtime -1
find . -type f -mmin -30`-newer 파일`은 그 파일보다 내용이 나중에 바뀐 것을 고른다. GNU find의 `-newermt`는 날짜 문자열과 비교한다.
find . -type f -newermt '2026-10-01'접근 시각으로 고르면 그 검색이 접근 시각을 다시 바꿀 수 있다. 파일 시스템을 `noatime`으로 마운트했다면 `-atime`은 기대와 다르다.
소유자와 권한
`-user`와 `-group`은 이름이고, `-uid`와 `-gid`는 숫자다. `-nouser`와 `-nogroup`은 계정 목록에 없는 소유자다. 백업을 다른 시스템으로 옮긴 뒤 남은 파일을 찾을 때 쓴다.
find /home -xdev -nouser -o -nogroup이 식은 AND가 OR보다 먼저라서 `-xdev`가 `-nouser`에만 붙는다. 두 검사 모두 같은 파일 시스템에 한정하려면 괄호가 필요하다.
find /home -xdev \( -nouser -o -nogroup \)`-perm`은 세 가지다.
- `-perm 644`는 권한이 정확히 그 값인 파일만 맞는다. 그룹 쓰기만 있는 파일을 `-perm g=w`로 찾으면, 다른 비트가 하나도 없는 `0020`만 맞는다. - `-perm -644`는 적은 비트가 모두 켜져 있으면 맞는다. 더 열려 있어도 된다. 그룹에 쓰기 권한이 있는 파일은 `-perm -g=w`다. - `-perm /111`은 적은 비트 중 하나라도 켜져 있으면 맞는다. 실행 비트가 주인, 그룹, 기타 중 어디에라도 있는 파일이다.
`/mode`에 켜진 비트가 하나도 없으면 모든 파일과 맞는다. `-perm +mode`는 2005년부터 폐기되었고 지금은 `/mode`를 쓴다.
find . -type f -perm -0002
find . -type d -perm -0002`-0002`는 기타 사용자 쓰기 비트가 켜진 것이다. 디렉터리의 이 비트는 아무나 그 안에 파일을 만들 수 있다는 뜻이라 일반 파일과는 다르게 봐야 한다.
`-readable`, `-writable`, `-executable`은 지금 사용자가 그 파일에 대해 그 권한이 있는지를 `access`로 확인한다. ACL까지 반영한다. `-perm`은 모드 비트만 본다. NFS처럼 서버가 uid를 바꿔 주는 환경에서는 `access` 결과가 서버의 판단과 다를 수 있다.
출력
`-print`는 경로를 한 줄에 하나씩 낸다. 파일 이름에 줄바꿈이 들어 있으면 한 파일이 두 줄처럼 보인다. 이름을 다른 명령에 넘길 때는 `-print0`로 널 문자로 끝내고, 받는 쪽은 널로 나눈다.
find . -type f -name '*.txt' -print0 | xargs -0 grep -H -n -- 'TODO'`xargs`에 `-0` 없이 넘기면 공백과 따옴표로 이름이 쪼개진다. `-print`의 결과를 그대로 `xargs`나 `for` 루프의 단어 나누기에 넣지 않는다.
`-ls`는 inode, 블록, 권한, 소유자, 크기를 `ls -dils`와 비슷하게 보여 준다. `-printf`는 형식을 직접 적는다. `%p`는 경로, `%s`는 바이트 크기, `%TY-%Tm-%Td`는 수정 날짜, `\n`은 줄바꿈이다. `\n`을 빼면 결과가 한 줄로 붙는다.
find . -type f -printf '%s %p\n' | sort -n`-fprintf 파일 형식`은 같은 출력을 파일로 보낸다.
명령 실행
`-exec`은 찾은 경로마다 명령을 실행한다. `{}`가 경로로 바뀌고, `\;`가 한 파일씩 실행하라는 끝이다. 세미콜론을 역슬래시 없이 쓰면 셸이 명령의 끝으로 가져간다.
find . -type f -name '*.bak' -exec rm -f -- {} \;`{} +`는 경로를 여러 개씩 모아 명령을 덜 실행한다. `xargs`와 비슷하고, `{}`는 명령의 마지막 인자여야 한다.
find . -type f -name '*.txt' -exec grep -H -n -- 'TODO' {} +명령이 이름만 있으면 `PATH`에서 찾는다. `-exec`의 작업 디렉터리는 find를 시작한 곳이지, 파일이 있는 디렉터리가 아니다. 파일 안에서 상대 경로로 다른 파일을 여는 프로그램이라면 기대와 다를 수 있다.
`-execdir`은 그 파일이 있는 디렉터리로 옮겨 실행하고, `{}`를 `./이름` 형태로 넘긴다. 검색 도중 상위 디렉터리가 바뀌는 경쟁에서 `-exec`보다 범위가 좁다. `PATH`에 현재 디렉터리가 들어 있으면 `-execdir`이 그 디렉터리의 실행 파일을 집을 수 있으니, `PATH`는 신뢰하는 경로만 둔다.
`-ok`는 `-exec`과 같지만 실행 전에 확인을 묻는다.
찾은 경로를 셸 명령 문자열 안에 이어 붙이면 이름 안의 따옴표와 공백이 문법이 된다. `-exec`에 경로를 인자로 넘기고, 셸이 필요하면 스크립트 안에서 `"$1"`로 받는다.
find . -type f -name '*.log' -exec sh -c 'gzip -c -- "$1" > "$1.gz"' sh {} \;`sh` 다음의 `sh`는 `$0`이다. 그 다음 `{}`가 `$1`이 된다.
삭제
`-delete`는 찾은 파일을 지운다. `-depth`가 포함되므로 안쪽 파일부터 지우고 빈 디렉터리를 지울 수 있다. 지운 뒤에는 휴지통이 없다.
조건 없이 `find . -delete`를 실행하면 시작 디렉터리 아래가 비워진다. 삭제는 출력을 눈으로 확인한 다음에 붙인다.
find . -name '*.tmp' -type f -print
find . -name '*.tmp' -type f -delete`-delete`는 경로가 `/`로 시작하지 않는 형태를 거부하므로 `find . -delete`처럼 상대 경로로 시작하는 검색에서 쓴다. 권한 때문에 지우지 못하면 find는 오류로 끝난다. 검색 도중에 파일이 이미 사라지는 것은 `-ignore_readdir_race`로 조용히 넘길 수 있다.
자주 쓰는 형태
최근 하루 안에 바뀐 일반 파일을 본다.
find . -type f -mtime -1100메비바이트보다 큰 파일을 큰 것부터 본다.
find . -type f -size +100M -printf '%s %p\n' | sort -n비어 있는 디렉터리를 본다. 지우기 전에 목록을 먼저 본다.
find . -type d -empty -print`node_modules`와 `.git`은 들어가지 않고, 소스에서 `TODO`를 찾는다.
find . \( -type d -name node_modules -o -type d -name .git \) -prune -o \
-type f -name '*.ts' -print0 | xargs -0 grep -n -- 'TODO'소유자가 없는 파일을 한 파일 시스템 안에서 찾는다.
find / -xdev \( -nouser -o -nogroup \) -print현재 디렉터리 바로 아래의 디렉터리만 나열한다.
find . -mindepth 1 -maxdepth 1 -type d -printf '%f\n'`%f`는 디렉터리를 뺀 이름이다.
다른 find와 다를 때
macOS와 BSD의 find에는 `-printf`, `-delete`, `-maxdepth`의 위치가 다르거나 옵션이 없다. BSD는 `-maxdepth`를 식으로 받지 않고 옵션으로 두는 판이 있다. `-regex`의 기본 문법도 다르다.
리눅스 밖으로 가져갈 스크립트는 `-name`, `-type`, `-mtime`, `-size`, `-perm`, `-exec`, `-print`처럼 공통인 검사부터 적는다. GNU만의 출력 형식이 필요하면 `find --version`을 보고 `-printf`를 쓴다.
GNU find 설명서는 다음에 있다.
결과가 어긋날 때
따옴표 없는 `*`는 find가 아니라 셸이 확장한다. 현재 디렉터리에 맞는 파일이 있으면 find는 패턴이 아니라 그 이름들을 받는다.
`-name '*.txt' -o -name '*.md' -type f`는 `-type f`가 두 번째 이름에만 붙는다. AND가 OR보다 먼저이기 때문이다. 공통 조건은 괄호 밖에 한 번만 두거나, 괄호 안에서 이름들을 묶는다.
`-mtime +1`은 어제 파일이 아니다. 48시간이 지난 파일이다. 최근 24시간은 `-mtime -1`이다.
`-size -1M`은 1바이트짜리 파일을 포함하지 않고 빈 파일 쪽만 맞춘다. 바이트로 자르려면 `c`를 붙인다.
`-path`와 `-regex`는 `./`로 시작하는 경로 전체와 비교한다. 패턴에 `./`를 빠뜨리면 시작 경로가 `.`일 때 하나도 맞지 않을 수 있다. 파일 이름만 볼 거면 `-name`을 쓴다.
기본 출력의 한 줄이 한 파일이라는 가정은 이름에 줄바꿈이 있으면 깨진다. 다음 명령으로 넘길 때는 `-print0`와 `xargs -0`, 또는 `-exec {} +`를 쓴다.
`-delete`와 `-exec rm`은 미리보기가 없다. 같은 식에 `-print`만 넣어 목록을 본 뒤에 삭제로 바꾼다.
COMMENTS
댓글 0
이 글에 대한 의견이나 질문을 남겨 주세요.
댓글은 구글이나 카카오로 시작한 뒤에 남길 수 있습니다.
등록된 댓글이 없습니다.