GNU find는 디렉토리 트리를 따라 각각의 파일 이름을 주어진 표현식들에 적용하여 매칭되는 결과를 프린트하거나 이를 이용해 별도의 명령을 실행할 수 있다. 간단한 파일 찾기 명령이라고 생각할 수 있지만, find는 실제로 엄청 다양한 일을 할 수 있으며 쉘을 통한 시스템 관리에 있어서 가장 유용하고 필수적인 도구라 할 수 있다. 실제로 grep의 경우 이를 drop in으로 대체하려는 프로젝트들도 많이 있지만, GNU find의 경우에는 이를 100% 커버할 수 있는 대체품이 아직 나오지 않은 상황이다. fd라는 툴이 빠른 속도와 편의성을 개선하여 인기를 얻고 있지만 모든 기능을 대체할 수는 없다.
참고로, GNU find는 그 이름에 있어서 너무 일반적인 동사를 사용하고 있어서 관련 자료를 찾으려 할 때 상관없는 문서가 너무 많이 검색된다. 이 때에는 “GNU find”라고 검색하는 것이 도움이 될 것이다. 이 글의 이후에서는 find를 쓰도록 할 것이다. 또한 윈도에서도 find.exe 라는 도구가 포함되어 있는데, 이것은 grep 처럼 파일에서 텍스트를 검색하는 도구이다. msys를 설치하면 윈도 명령 프롬프트에서도 GNU find를 사용할 수 있지만, 둘의 이름이 갖기 때문에 한쪽의 이름을 바꿔둬야 한다.
사용법
<strong>find</strong> [-H] [-L] [-P] [-D debugopts] [-Olevel] [path...] [expression]
find는 단순히 파일을 찾는 것 뿐만 아니라, ‘어떻게 처리할 것인가’에 대한 표현식을 처리할 수 있는 작은 스크립트 언어처리기처럼 작동할 수 있다. 명령의 구조는 링크를 다룰 옵션과 디버그 옵션, 최적화수준 정도의 간단한 옵션이 있으며, 시작 위치를 기준으로 여러 표현식으로 필터링과 처리 방법을 지정하게 된다.
옵션
-H
, -L
, -P
는 심볼릭 링크를 어떻게 다룰 것인지를 결정한다. 이후에 오는 인자들은 시험할 파일이나 디렉토리의 이름들이다. 이후에 -
, (
, !
로 시작하는 인자를 만나면 여기서부터는 조건식으로 평가되며 이들은 찾고자 하는 조건이 된다. 만약 경로가 주어지지 않으면 현재 경로를 기준으로 찾는다. 또한 표현식이 주어지지 않으면 기본적으로 -print
가 지정된다.
심볼릭링크
하나 이상의 옵션이 붙으면 맨 뒤의 옵션이 적용된다.
-P
: 심볼릭 링크 무시
-L
: 심볼릭 링크를 따라간다.
-H
: 명령줄 인자를 제외한 심볼릭 링크를 따르지 않는다.
디버그 옵션 및 최적화 수준
-D
debugopts:
help
: 디버그 옵션을 보여준다.tree
: 표현식 트리를 보여준다.stat
: 파일들에 대한 stat 시스템 콜을 보여준다.opt
: 표현식 트리에 대한 분석 정보를 표시한다.rates
: 예측치에 대한 평가를 표시한다.
-O
level: 최적화 정도를 지정할 수 있다. (사실 정확히 무슨 의미인지는 모르겠당…)
표현식
표현식은 탐색 조건들과 테스트 그리고 액션으로 구성된다. -and
는 생략된 연산자로 취급한다.
옵션
-d
:-depth
의 약어. 해당 디렉토리를 처리하기 이전에 하위 디렉토리를 먼저 처리하도록 한다.-delete
액션은 이 옵션을 기본적으로 갖는다.-daystart
: 시간 관련 테스트(-amin
,-atime
,-cmin
,-ctime
,-mmin
,-mtime
)의 기준 시간을 24시간 이전이 아닌 오늘 자정으로 쓴다.-maxdepth
levels: 하위 디렉토리 처리를 제한한다.-mindepth
levels: 주어진 디렉토리만큼 내려간 디렉토리만 처리한다.-noleaf
: UNIX 파일 시스템이 아닐 때. 이는 디렉토리가 기본적으로 이름과 “.” 이라는 두 개의 노드를 가지고 있다고 가정하고 최적화하는데, 이 옵션은 이 최적화를 쓰지 않는다. MS-DOS 등에서 성능이 좋아진다.-regextype
type: 정규식 사용시의 정규식의 타입. emacs가 기본이며, posix-awk, posix-basic, posix-egrep, posix-extended 등에서 고르면 된다.
TEST
-newerXY
나-samefile
과 같은 일부 테스트들은 현재 파일과 주어진 파일을 비교한다. 테스트는 반복되겠지만, 인자로 주어진 파일은 첫 명령 입력시에 파싱되어 한번만 쓰인다.- 아래에 n으로 쓰는 것은 숫자값인데 앞에 +/- 붙어서 보다 크다, 보다 작다를 의미할 수 있다.
-amin
n : n 분 전에 최종 액세스한 파일-anewer
file: 주어진 파일이 수정된 시각보다 이후에 액세스된 파일.-atime
n: n * 24시간 이전에 최종 액세스된 파일. 따라서atime +1
하면 이틀 전에 액세스한 파일이 된다.-cmin
n: n 분 전에 변경된 파일-cnewer
file: 주어진 파일의 수정 시간 이후에 수정되었는지ctime
n: n * 24 시간 이전에 변경된 파일.-empty
: 빈 파일 혹은 디렉토리-executable
: 실행가능한지-false
: 무조건 실패fstype
type: 파일 시스템 타입-ipath
pattern: 쓰이지 않으며-iwholename
과 동일하다.-iregex pattern
: 대소문자 구분없는 정규식 매칭-iwholename
pattern:-wholename
과 같으나 대소문자 무시-mmin
n: 파일이 n 분 전에 수정됨-mtime
n: 파일이 어제기준 24 * n 시간 전에 수정됨.-name
pattern: 파일의 베이스 이름(디렉토리 이름 뗀)이 주어진 패턴과 매치하는지 본다.-newer
file: 주어진 파일보다 최근에 수정되었는지-newerXY
reference: 주어진 레퍼런스보다 최근인지-path
pattern: 전체 경로가 주어진 패턴인지 본다. 이 때, ‘/’, ‘.’ 는 특별한 의미를 가지지 않는다.-regex
pattern: 파일의 전체 경로를 주어진 패턴과 매치되는지 검사한다.-samefile
name: 주어진 이름의 파일과 같은 파일인지 검사한다.-size
n[cwbkMG]: 특정 크기인지 본다. 보다 크기거나 작거나를 정할 수 있다.-true
: 항상 참-type
c: 타입을 검사한다.-used
n : 상태가 변경된지 n 일 이후에 사용되었는지를 확인한다.-wholename
pattern: 전체 경로를 비교한다.-path
와 같은 옵션이다.
액션
기본적으로 액션은 파일 명들을 출력한다.
-print
: 기본 액션. 테스트를 통과한 파일명들을 출력한다.-fprint
FILE: 파일에 결과를 쓴다.-ls
:ls -dils
포맷을 출력해준다.-fls
FILE: 파일에-ls
옵션의 결과를 쓴다.-print0
: 개행이 아닌 널 문자로 구분하여 출력한다. 이는xargs
로 결과를 넘겨서 쓸 때 유용하다.-printf
,-fprintf
FORMAT: 주어진 포맷에 맞추어 출력한다.-prune
: 만약 발견한 파일이 디렉토리라면, 그 아래로 내려가지 않는다. 만약-depth
가 있다면 무시되며,-depth
가 우선한다.-delete
: 발견된 모든 결과를 제거한다. 사용시 매우 주의가 필요하며 (까딱 하위 디렉토리 내 파일까지 다 날려버린다든지…) 꼭 출력해 본 다음에 실행할 것-exec
COMMAND ; : 주어진 명령을 실행한다.;
으로 꼭 끝나야 하는데 이는 이스케이프될 필요가 있다. (MS-DOS에서는 안해도 되더라) 파일의 이름이 들어갈 자리는{}
으로 지정해주면 된다.-exec
COMMAND {} + : 이는 각각의 파일 이름을 뒤에다 붙이는 식으로 파일 개수보다 생성되는 명령의 수가 적다.-execdir
COMMAND ; :-exec
는 시작 디렉토리 기준으로 명령을 실행하는데-execdir
은 매 서브 디렉토리에서 명령을 수행한다.
연산자
우선순위가 높은 것부터 쓴다. 위의 표현식들은 EXPR로 표기한다.
(
EXPR)
: 괄호로 둘러싼 영역을 우선 평가한다.!
EXPR,-not
EXPR : 결과를 반대로- EXPR1 EXPR2: 두 표현식이 나란히 써지면
AND
로 연결한다. - EXPR1
-a
EXPR2 :AND
로 연결 - EXPR1
-o
EXPR2 : OR로 연결한다. 만약 앞쪽의 내용이 참이면 뒤 쪽은 평가되지 않는다. - EXPR1
,
EXPR2 : 두 표현식을 리스트로 만든다. 앞 표현식의 값과 상관없이 두 개의 표현식은 항상 평가되며, 앞의 결과는 그냥 무시된다.
예제
몇 가지 실행 예제를 소개하겠다. 다음은 가장 간단한 형태로, 특정 경로 아래에 있는 파일들을 모두 삭제하는 명령이다.
특정 파일들을 한꺼번에 삭제하기 (배치실행 방식과 반복실행 방식)
find /tmp -name core -type f -print | xargs /bin/rm -f
/tmp 하위의 core라는 이름의 파일을 모두 찾은 다음 이들을 몽땅 지운다. 단, 이 명령은 파일 이름에 공백이 들어있거나 하면 xargs
에서 파싱을 잘못하므로 실패할 가능성이 있다. 이 문제를 방지하려면, 각각의 결과 이름을 공백으로 분리하지 않고 NULL문자로 대체하여 다음과 같이 실행한다.
find /tmp -name core -type f -print0 | xargs -0 /bin/rm -f
파이프와 xargs를 이용하면, 모든 결과를 취합하여 이를 하나의 명령으로 처리하게 되는데, 이 방식 외에도 find는 -exec
액션을 사용하여, 매 결과에 대해서 각각 명령을 처리할 수 있다.
find . -type f -exec file {} \;
위 명령은 모든 파일을 찾아서 file 명령을 적용한다.
복합 명령 적용하기
다음의 예제는 일종의 분기를 구현한 것이다. 콤마로 연결한 두 표현식(액션)은 앞 표현식의 결과에 상관없이 모두 평가되며 최종적으로 뒤의 것으로 평가된다. 이를 통해 한 번의 find 명령으로 여러 가지 액션을 선택적으로 취하게 할 수 있다.
find / \
\( -perm -4000 -fprintf /root/suid.txt %#m %u %p\n \) , \
\( -size +100M -frpintf /root/big.txt %-10s %p\n \)
위 명령은 ( ) 를 사용해서 두 개의 액션을 순차적으로 실행해서, 한 번의 명령으로 두 번의 find를 실행한 것과 동일한 효과를 낸다. 대신 전체 탐색은 한 번만 수행하므로 두 번 실행하는 것보다는 수행 시간이 훨씬 짧다.
- 첫 번째 그룹에서는 권한값이 4000 미만인지를 기준으로 필터링하고, 그 결과를 /root/suid.txt 파일에 기록하며,
- 두 번째 그룹에서는 크기가 100MB 이상이면 /root/big.txt에 기록한다.
find에서 모든 액션은 표현식이므로 이런식으로 조합하여 다양한 효과를 낼 수 있다. 다음 명령의 경우에는 JPG파일과 PNG 파일을 찾아서 각각 다른 파일에 그 이름을 기록해준다.
find /d/pics \( -type f -name *.jpg -fprint jpg.txt \) , \
\( -type f -name *.png -fprint png.txt \)
시간과 관련된 필터링
-*time
, -*min
으로 최근에 수정/액세스한 파일을 필터링할 수 있으며, -anewer
, -mnewer
등으로 특정 파일보다 이후에 액세스되었거나 수정된 파일을 찾을 수 있다.
find ~ -mtime 0
으로 홈 디렉토리 내에서 24시간 이내에 수정된 파일을 찾을 수 있다.
권한과 관련된 필터링
find /sbin /usr/sbin -executable \! -readable -print
/sbin, /usr/sbin 에서 실행 가능하지만, 읽을 수 없는 파일을 찾아서 출력. \!
은 -not
과 같은 연산으로 -not -readable
로 해석할 수 있다. 다음과 같이 -perm
을 사용하여 특정 권한의 파일만 찾을 수도 있다.
find . -perm 664
다음의 예제는 조금 복잡해 보일 수 있는데…. 특정 조건에 맞는 파일을 다른 위치로 백업하는 명령이다.
cd /source-dir
find . -name .snapshot -prune -print0 -o \( \! -name *~ -print0 \) | cpio -pmd0 /dest-dir
-name .snapshot
: 이름이 .snapshot 인지 체크한다.-prune
: 디렉토리라면 그 아래로 내려가지 않는다. (해당 디렉토리만 선택된다)-print0
: 그리고 해당 디렉토리를 출력한다.-o
:OR
\( -not -name "*~" -print0 \)
:.snapshot
이 아니라면 이름이~
으로 끝나지 않는 모든 파일을 NULL문자종결로 출력한다.
-name .snapshot # 이름이 .snapshot 인가?
-prune # .snapshot 이 디렉토리라면 더 이상 하위 레벨로 들어가지 마라.
-o # 이름이 .snapshot이 아닐 때만 true 이다.
\( \! -name *~ # 백업파일이 아니면 (이름이 ~로 끝나지 않는다면)
-print0 # 널문자로 구분하여 출력한다.
\)
즉 이 명령은 현재 위치에서 하위 경로의 .snapshot 디렉토리와, 그 외의 파일 중에서는 백업 파일이 아닌 모든 파일을 다른 곳으로 복사하여 백업하는 명령이다. 전체적으로 상당히 복잡한데, 조금 더 간단한 예를 들어보자.
만약 .vim 디렉토리에서 번들의 .git 을 제외하고 탐색하는 방법을 이를 응용해보면 다음과 같이 쓸 수 있다. (다만, 각각의 .git 은 출력된다.)
find ~/.vim -name .git -prune -o -type f -name "*.vim"
.git 디렉토리들을 화면에 출력하지 않으려면 -fprint
로 임의의 파일로 보내어 화면에는 .vim 파일들만 보이게 할 수 있다.
find ~/.vim -name .git -prune -fprint skipped.txt -o -type f -name *.vim -print
물론, .git 내부에는 .vim 파일이 들어있지는 않겠지만, -prune을 사용하여 가드하면 해당 디렉토리 아래를 훑어보는 과정을 생략하기 때문에 전체 수행 시간을 매우 단축할 수 있다.
반복해서 말하지만, 각각의 액션들은 모두 평가식이며 -a , -o 및 , 연산자를 통해서 조합할 수 있다. 따라서 다음과 같은 식으로도 명령을 구성할 수 있다.
find repo/ \
-exec \test -d {}/.svn -o -d {}/.git -o -d {}/CVS ; \
-print -prune
두 번째 라인 전체가 -exec
평가식이며, 여기서 쓰인 -o
는 test
명령의 옵션이다. (역시 의미는 OR) 액션은 명령이 에러 없이 끝나면 항상 참으로 평가되므로, -exec
절에 의해서 test
명령이 true (0)를 리턴했다면, 이 절은 참으로 평가된다. 이어지는 -print -prune
은 암묵적으로 and로 연결된다.
따라서 test
명령으로 조건을 체크하고 참인 경우에만 뒤의 절들이 실행되며, 이들 디렉토리는 이름만 출력하고 내부로 내려가지는 않게 될 것이다.