BASH – find 명령 사용법

find

GNU find는 디렉토리 트리를 따라 각각의 파일 이름을 주어진 표현식들에 적용하여 매칭되는 결과를 프린트하거나 이를 이용해 별도의 명령을 실행할 수 있다.

사용법

find [-H] [-L] [-P] [-D debugopts] [-Olevel] [path…] [expression]

옵션

-H, -L, -P는 심볼릭 링크를 어떻게 다룰 것인지를 결정한다. 이후에 오는 인자들은 시험할 파일이나 디렉토리의 이름들이다. 이후에 -, (, !로 시작하는 인자를 만나면 여기서부터는 조건식으로 평가되며 이들은 찾고자 하는 조건이 된다. 만약 경로가 주어지지 않으면 현재 경로를 기준으로 찾는다. 또한 표현식이 주어지지 않으면 기본적으로 -print가 지정된다.

심볼릭링크

하나 이상의 옵션이 붙으면 맨 뒤의 옵션이 적용된다.

-P: 심볼릭 링크 무시

-L: 심볼릭 링크를 따라간다.

-H: 명령줄 인자를 제외한 심볼릭 링크를 따르지 않는다.

디버그 옵션

-D debugopts:

  • help: 디버그 옵션을 보여준다.
  • tree: 표현식 트리를 보여준다.
  • stat: 파일들에 대한 stat 시스템 콜을 보여준다.
  • opt: 표현식 트리에 대한 분석 정보를 표시한다.
  • rates: 예측치에 대한 평가를 표시한다.

-Olevel: 최적화 정도를 지정할 수 있다.

표현식

표현식은 옵션들과 테스트 그리고 액션으로 구성된다. -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가 있다면 취소된다.
  • -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에서 파싱을 잘못하므로 실패할 가능성이 있다.

find /tmp -name core -type f -print 0 | xargs -0 /bin/rm -f

위에서 생기는 문제를 해결할 수 있다.

find . -type f -exec file {} \;

모든 파일을 찾아서 file 명령을 적용한다. 다음의 예제는 일종의 분기를 구현한 것이다. ,로 연결한 두 표현식은 앞 표현식의 결과에 상관없이 모두 평가되며 최종적으로 뒤의 것으로 평가된다.

find / \
\( -perm -4000 -fprintf /root/suid.txt %#m %u %p\n \) , \
\( -size +100M -frpintf /root/big.txt %-10s %p\n \)

모든 파일을 찾아서,

  1. 권한값이 4000 미만이면 /root/suid.txt 파일에 기록하고
  2. 크기가 100MB 이상이면 /root/big.txt에 기록한다.

액션 역시 표현식이므로 이런식으로 두 분류를 지정할 수 있다. 즉 A -> B, C -> D의 표현식 4개가 연결되어 있어서 A가 참이면 B가 평가되고, 그 결과에 상관없이 C가 다시 평가되고 C가 참이면 D가 평가(실행)되는 것이다. 좀 더 읽기 쉽게 만들어보면 다음의 예처럼 쓸 수 있다.

find /d/pics \( -type f -name *.jpg -fprint jpg.txt \) , \
\( -type f -name *.png -fprint png.txt \)

이런식으로 써서 한 번의 탐색으로 JPG 파일과 PNG 파일을 분류해서 각각의 결과를 개별 파일에 저장하는 것이 가능하다.

find $HOME -mtime 0 으로 홈 디렉토리 내에서 24시간 이내에 수정된 파일을 찾을 수 있다.

find /sbin /usr/sbin -executable \! -readable -print -> /sbin, /usr/sbin 에서 실행 가능하지만, 읽을 수 없는 파일을 찾아서 출력

find . -perm 664 : 664 권한인 파일 찾기

다음의 예제는 조금 복잡해 보일 수 있는데….

cd /source-dir
find . -name .snapshot -prune -o \( \! -name *~ -print0 \) | cpio -pmd0 /dest-dir

제법 복잡해 보이는데, 이 명령은 /source-dir의 파일들을 /dest-dir로 복사한다. 하지만 이름이 .snapshot인 파일이나 디렉토리(그리고 그 안의 내용)은 생략한다. 표현식 부분만 평가해보자.

-name .snapshot  # 이름이 .snapshot 인가?
   -prune        # 디렉토리라면 더 이상 하위 레벨로 들어가지 마라. 그리고 true이다.
     -o            # 앞이 true 니까 뒤도 평가한다. 
       \( \! -name *~ # 백업파일이 아니면 (이름이 ~로 끝나지 않는다면)
                 -print0 # 널문자로 구분하여 출력한다. 
       \)

-o의 앞쪽의 표현식은 -prune 으로 끝났으므로 일단 print 되지 않는다. 그리고 -prune은 일단 true값이므로 뒤로 이어져서 백업이 아닌 파일들이 프린트되는 셈이다.

만약 .vim 디렉토리에서 번들의 .git 을 제외하고 탐색하는 방법을 이를 응용해보면 다음과 같이 쓸 수 있다.

find ~/.vim -name .git -prune -o -type f -name *.vim -print
--해석하자면
find ~/.vim   # .vim에서 찾기를
     -name .git -prune # .git 이라는 이름을 찾으면 더 이상 하위로 내려가지 마라, 
     -o # 이름이 .git이 아니면 뒤의 절을 평가하라. 아니면 여기서 끝.
     -type f -name *.vim # 이름이 .vim으로 끝나는 파일이면
     -print 

-prune 뒤에 화면에 출력되지 않은 항목을 따로 파일로 기록하도록 -o 앞에 표현식을 다음과 같이 추가할 수 있다.

find ~/.vim -name .git -prune -fprint skipped.txt -o -type f -name *.vim -print

실제로 앞쪽에 .git을 들어가지 않도록 하는 가드 절이 없으면 번들 디렉토리의 탐색은 10초 가량이, 가드 절이 있는 경우에는 1초 가량이 걸렸다.

find의 일부 예제를 보고 “위치 > 조건매칭 > 결과에 대한 조작”으로 생각하기 쉬운데 여러 액션들 역시 “평가식”이다. 따라서 다음과 같은 조작도 가능한데,

find repo/ \
-exec \test -d {}/.svn -o -d {}/.git -o -d {}/CVS ; \
-print -prune

이 명령을 보자. 평가 자체를 -exec로 하고 있다. 여기서 쓰인 -otest 명령의 옵션이다. (역시 의미는 OR) 그리고 이 두번째행은 하나의 -exec 표현식이다. 액션은 항상 참으로 평가되므로,  서브디렉토리 내의 파일을 제외한 모든 이름이 출력된다. 따라서 주어진 이름 뒤에 CVS, .svn, .git 이 붙어 있는 디렉토리가 있으면 이를 출력하고 해당 디렉토리로는 들어가지 않는다는 뜻이다.

repo/project1/CVS
repo/gnu/project2/.svn
repo/gnu/project3/.svn
repo/gnu/project3/src/.svn
repo/project4/.git