find

find 명령은 파일을 검색하는데 사용할 수 있는 많은 옵션들을 제공합니다. 필요로 하는 옵션에 대해서는 man 페이지를 참조하시고 여기서는 기본적인 find 명령의 사용법에 대해서 알아보겠습니다.

앞으로 설명을 위한 명령 실행에는 다음과 같은 디렉토리 구조를 대상으로 하겠습니다.

$ find
.
./dir_2
./dir_2/bar_1.html
./dir_2/dir_3
./dir_2/dir_3/bar_2.txt
./dir_2/dir_3/bar_2.html
./dir_2/bar_1.txt
./.hidden.txt
./index.txt
./index.html
./.git
./.git/git.txt
./.git/git.html
./dir_1
./dir_1/foo.html
./dir_1/foo.txt

위의 출력에서 보는 것과 같이 find 명령은 아무런 인수를 주지 않으면 find . 과 같습니다. 따라서 출력의 앞부분에 ./ 가 포함되는데 이것이 필요하지 않을 경우 다음과 같이 하면 됩니다.

# 디렉토리, 파일명을 직접 적어준다.
$ find dir_1
dir_1
dir_1/foo.html
dir_1/foo.txt

# 또는 디렉토리 전체를 검색하려면 다음과 같이 shell globbing 을 사용하면 됩니다.
# "*" glob 문자는 기본적으로 "." 으로 시작하는 hidden 파일은 매칭에 포함되지 않기 때문에
# .git 디렉토리 내용은 포함되지 않는 것을 볼 수 있습니다.
$ find *
dir_1
dir_1/foo.html
dir_1/foo.txt
dir_2
dir_2/bar_1.html
dir_2/dir_3
dir_2/dir_3/bar_2.txt
dir_2/dir_3/bar_2.html
dir_2/bar_1.txt
index.html
index.txt

# "." 로 시작하는 파일도 매칭에 포함하려면 다음과 같이 합니다.
$ find -printf '%P\n'
# 또는
$ find * .[^.]*
. . .
. . .
dir_2/dir_3/bar_2.html
dir_2/bar_1.txt
index.html
index.txt
.git
.git/git.txt
.git/git.html
.hidden.txt

find 명령은 표현식( expression ) 으로 구성됩니다.

여기에는 tests, actions, operators 등이 있습니다.

-maxdepth -mindepth 같은 것은 따로 global options 라고 합니다.

tests

-name '*.sh', -type f, -mtime -1, -size +1M . . . 같은 표현식이 test 에 해당합니다.

actions

-print, -prune, -delete, -exec, -ls, -quit . . . 등은 action 에 해당합니다.

operators

우선순위가 높은 순서부터입니다.

  1. ( . . . )
  2. -not, !
  3. -and, -or 또는 -a, -o ( 우선순위는 -and 가 높습니다 )

tests 와 actions 은 참, 거짓을 반환하므로 -and, -or 연산자를 이용해 연결할 수 있습니다.

# index.html 파일에 대해 -type d 테스트는 거짓이므로 값이 표시되지 않는다.
$ find index.html -type d -and -name '*.html'

$ find index.html -type f -and -name '*.html'      # -type f 로 변경
index.html

# index.html 파일에 대해 -exec test -d {} \; 액션은 비정상 종료 상태 값을 반환하므로
# 뒤이어지는 -exec echo {} \; 는 실행되지 않는다.
$ find index.html -exec test -d {} \; -and -exec echo {} \;

$ find index.html -exec test -f {} \; -and -exec echo {} \;   # test -f 로 변경
index.html

기본적으로 -and 연산자는 생략해서 사용할 수 있습니다.

$ find index.html -type f -name '*.html'
index.html

$ find index.html -type d -name '*.html'       # -type d
$

# -or 연산자는 생략할 수 없습니다.
$ find index.html -type d -o -name '*.html'
index.html

test 에는 하나 이상의 action 을 붙여 사용할 수 있습니다.

test 에는 출력을 위해 기본적으로 -print 액션이 적용됩니다.
따라서 다음 두개로 구성된 명령들은 서로 같은 것입니다.

$ find * -name '*.html'
$ find * -name '*.html' -print

$ find index.html -type d -o -name '*.html'
index.html
$ find index.html -type d -print -o -name '*.html' -print
index.html

$ find -name .git -prune
./.git
$ find -name .git -prune -print
./.git

하지만 다음과 같이 test 에 직접 -prune 을 제외한 action 이 사용되면 다른 test 에는 디폴트로 -print 가 적용되지 않게 됩니다.

# -name '*.txt' 테스트에 직접 -print 액션이 사용되어 
# -name '*.html' 테스트에는 -print 가 디폴트로 적용되지 않는다.
$ find * -name '*.html' -o -name '*.txt' -print
dir_1/foo.txt
dir_2/dir_3/bar_2.txt
dir_2/bar_1.txt
index.txt

# -name '*.txt' 에는 -print 가 적용되지 않는다.
$ find * -name '*.html' -print -o -name '*.txt'
dir_1/foo.html
dir_2/bar_1.html
dir_2/dir_3/bar_2.html
index.html

# find * -name '*.html' -o -name '*.txt' 와 같은 것임
$ find * -name '*.html' -print -o -name '*.txt' -print
dir_1/foo.html
dir_1/foo.txt
dir_2/bar_1.html
dir_2/dir_3/bar_2.txt
dir_2/dir_3/bar_2.html
dir_2/bar_1.txt
index.html
index.txt

-prune 액션은 특정 디렉토리를 검색에서 잘라낼 때 사용합니다. 매칭이 되었을 때 뒤이어지는 표현식이 실행되면 안되므로 -or 연산자를 사용합니다. 명령을 작성할 때 주의할 점은 다음 첫번째 명령과 같이 작성하게 되면 이것은 두번째 명령과 같게되므로 출력에 ./.git 이 남게 됩니다. 따라서 세번째와 같이 작성해야 ./.git 이 포함되는 것을 방지할 수 있습니다.

$ find -name .git -prune -o -name '*.html'
./dir_2/bar_1.html
./dir_2/dir_3/bar_2.html
./index.html
./.git                        <---- ./.git 이 포함된다.
./dir_1/foo.html

# 이것은 위 명령과 같은 것임
$ find -name .git -prune -print -o -name '*.html' -print

# 다음과 같이 작성해야 -prune -print 가 되지 않는다.
$ find -name .git -prune -o -name '*.html' -print
./dir_2/bar_1.html
./dir_2/dir_3/bar_2.html
./index.html                  <---- ./.git 이 제거 되었다.
./dir_1/foo.html

( . . . ) 연산자의 사용

( ) 연산자는 우선순위를 조절하는 용도 외에 중복방지용으로도 사용됩니다.

$ find -name .git -prune -o -name dir_1 -prune -o -name '*.html' -print
./dir_2/bar_1.html
./dir_2/dir_3/bar_2.html
./index.html

# 중복되는 -prune 액션을 ( ) 을 이용해 한번만 사용할 수 있습니다.
$ find \( -name .git -o -name dir_1 \) -prune -o -name '*.html' -print
./dir_2/bar_1.html
./dir_2/dir_3/bar_2.html
./index.html

# -print0 액션을 두개의 -name 테스트에 모두 적용
$ find \( -name '*.txt' -o -name '*.html' \) -print0

$ find \( -name '*.txt' -o -name '*.html' \) -printf '%P\0'    # \0 NUL 문자

$ find \( -name '*.txt' -o -name '*.html' \) -printf '%P\n'    # \n newline

time 테스트와 +n, -n, n 숫자 값의 사용

  • +n 은 greater than n
  • -n 은 less than n
  • n 은 exactly n

-mtime, -mmin ... 에 설정한 값은 기본적으로 현재 시간으로부터 계산을 합니다. 그러므로 -mmin -60 은 현재 시간으로부터 60 분 이내에 수정된 파일을 말하고 -mtime -1 는 현재 시간으로부터 1 일 이내 ( 24 시간 이내 ) 수정된 파일들을 말합니다. 따라서 현재 시간이 15 시 일경우 어제 18 시에 수정한 파일은 검색에 포함되지만 09 시에 수정한 파일은 포함되지 않게 됩니다.

만약에 시간 계산을 현재 시간으로부터가 아닌 from the beginning of today 부터 하려면 -daystart 옵션을 사용합니다.

다음은 현재 시각으로부터 수정한지 60 분 이내의 파일들을 검색하는 명령입니다.

# 1. -prune    : dir_1 과 dir_3 는 잘라내고
# 2. -type f   : 디렉토리는 제외하고 일반 파일만
# 3. -mmin -60 : 현재 시각으로부터 수정한지 60 분 이내의 파일만
$ find * \( -name dir_1 -o -name dir_3 \) -prune -o -type f -mmin -60 -exec ls {} \;
dir_2/bar_1.html
dir_2/bar_1.txt
index.html
index.txt

# 수정한지 60 분이 지난 파일들
-mmin +60

# 수정한지 3 일 이내의 파일들
-mtime -3

# 수정한지 3 일이 지난 파일들
-mtime +3

# 수정한지 3 일째 되는 파일들만
-mtime 2

# 수정한지 1 일 이내의 파일들
-mtime -1 과 -mtime 0 은 같은 결과
------------------------------------------------------------

$ date     # 현재 11 일 12 시
Mon Jun 11 12:38:32 KST 2018 

# -mtime -1 ( 현재 시간으로 부터 1 일 이내 파일 )
# 현재 시간으로부터 계산을 하므로 날짜가 Jun 10 인 파일도 보인다.
$ find * -type f -mtime -1 -ls
6572539  8 -rw-rw-r-- 1 mug896 mug896  7757 Jun 10 13:36 eval.md
6572529 12 -rw-rw-r-- 1 mug896 mug896  8212 Jun 11 12:42 find.md
6571730 16 -rw-rw-r-- 1 mug896 mug896 15779 Jun 10 13:37 here_document.md
6572864  4 -rw-rw-r-- 1 mug896 mug896  2703 Jun 11 08:28 SUMMARY.md
6572862 12 -rw-rw-r-- 1 mug896 mug896  8902 Jun 10 15:30 test_operators.md

# -mtime 0 ( 현재 시간으로부터 1 일째 되는 파일. 위와 같은 결과 )
$ find * -type f -mtime 0 -ls
6572539  8 -rw-rw-r-- 1 mug896 mug896  7757 Jun 10 13:36 eval.md
6572529 12 -rw-rw-r-- 1 mug896 mug896  8212 Jun 11 12:42 find.md
6571730 16 -rw-rw-r-- 1 mug896 mug896 15779 Jun 10 13:37 here_document.md
6572864  4 -rw-rw-r-- 1 mug896 mug896  2703 Jun 11 08:28 SUMMARY.md
6572862 12 -rw-rw-r-- 1 mug896 mug896  8902 Jun 10 15:30 test_operators.md

# -mtime -3 ( 현재 시간으로부터 3 일 이내 파일 )
# 뒤에 나오는 exit_status.md 파일은 같은 Jun 8 이지만 여기에 추가되지 않는다.
$ find * -type f -mtime -3 -ls
6572539  8 -rw-rw-r-- 1 mug896 mug896  7757 Jun 10 13:36 eval.md
6560999 12 -rw-rw-r-- 1 mug896 mug896  8458 Jun 11 12:47 find.md
6571730 16 -rw-rw-r-- 1 mug896 mug896 15779 Jun 10 13:37 here_document.md
6570224  8 -rw-rw-r-- 1 mug896 mug896  7430 Jun  8 18:25 keyword_commands.md  <-- 추가
6572864  4 -rw-rw-r-- 1 mug896 mug896  2703 Jun 11 08:28 SUMMARY.md
6570196 12 -rw-rw-r-- 1 mug896 mug896 10343 Jun  8 18:20 test.md              <-- 추가
6572862 12 -rw-rw-r-- 1 mug896 mug896  8902 Jun 10 15:30 test_operators.md

# -mtime -4 ( 현재 시간으로부터 4 일 이내 파일 )
$ find * -type f -mtime -4 -ls
6572539  8 -rw-rw-r-- 1 mug896 mug896  7757 Jun 10 13:36 eval.md
6572535  8 -rw-rw-r-- 1 mug896 mug896  7212 Jun  8 12:35 exit_status.md       <-- 추가
6560999 12 -rw-rw-r-- 1 mug896 mug896  8458 Jun 11 12:47 find.md
6571730 16 -rw-rw-r-- 1 mug896 mug896 15779 Jun 10 13:37 here_document.md
6570224  8 -rw-rw-r-- 1 mug896 mug896  7430 Jun  8 18:25 keyword_commands.md
6572864  4 -rw-rw-r-- 1 mug896 mug896  2703 Jun 11 08:28 SUMMARY.md
6570196 12 -rw-rw-r-- 1 mug896 mug896 10343 Jun  8 18:20 test.md
6572862 12 -rw-rw-r-- 1 mug896 mug896  8902 Jun 10 15:30 test_operators.md

# -mtime 2 ( 현재 시간으로부터 3 일째 되는 파일 )
# exit_status.md 파일은 같은 Jun 8 이지만 여기에 포함되지 않는다.
$ find * -type f -mtime 2 -ls
6570224  8 -rw-rw-r-- 1 mug896 mug896  7430 Jun 8 18:25 keyword_commands.md
6570196 12 -rw-rw-r-- 1 mug896 mug896 10343 Jun 8 18:20 test.md

# -mtime 3 ( 현재 시간으로부터 4 일째 되는 파일 )
$ find * -type f -mtime 3 -ls
6572535  8 -rw-rw-r-- 1 mug896 mug896  7212 Jun 8 12:35 exit_status.md

-daystart 옵션을 사용하게 되면 날짜를 from the beginning of today 부터 계산을 하므로 파일의 날짜가 정확히 해당일에 매칭 되는 파일들이 출력됩니다.

$ date     # 현재 11 일 12 시
Mon Jun 11 12:38:32 KST 2018 

# -mtime -1 ( 1 일 이내 파일 )
# 정확히 날짜가 Jun 11 인 파일만 출력되고 Jun 10 인 파일들은 포함되지 않는다.
$ find * -daystart -type f -mtime -1 -ls
6560999 12 -rw-rw-r-- 1 mug896 mug896  9458 Jun 11 13:31 find.md
6572864  4 -rw-rw-r-- 1 mug896 mug896  2703 Jun 11 08:28 SUMMARY.md

# -mtime -2 ( 2 일 이내 파일 )
# 2 일 이내 이므로 Jun 10, 11  파일들이 출력된다.
$ find * -daystart -type f -mtime -2 -ls
6572539  8 -rw-rw-r-- 1 mug896 mug896  7757 Jun 10 13:36 eval.md
6560999 12 -rw-rw-r-- 1 mug896 mug896  9458 Jun 11 13:31 find.md
6571730 16 -rw-rw-r-- 1 mug896 mug896 15779 Jun 10 13:37 here_document.md
6572864  4 -rw-rw-r-- 1 mug896 mug896  2703 Jun 11 08:28 SUMMARY.md
6572862 12 -rw-rw-r-- 1 mug896 mug896  8902 Jun 10 15:30 test_operators.md

# -mtime 0 ( 1 일째 되는 파일 )
# 날짜가 Jun 10 인 파일들은 포함되지 않는다.
$ find * -daystart -type f -mtime 0 -ls
6560999 12 -rw-rw-r-- 1 mug896 mug896  9458 Jun 11 13:31 find.md
6572864  4 -rw-rw-r-- 1 mug896 mug896  2703 Jun 11 08:28 SUMMARY.md

# -mtime 1 ( 2 일째 되는 파일 )
# 정확히 날짜가 Jun 10 인 파일들만 출력된다.
$ find * -daystart -type f -mtime 1 -ls
6572539  8 -rw-rw-r-- 1 mug896 mug896  7757 Jun 10 13:36 eval.md
6571730 16 -rw-rw-r-- 1 mug896 mug896 15779 Jun 10 13:37 here_document.md
6572862 12 -rw-rw-r-- 1 mug896 mug896  8902 Jun 10 15:30 test_operators.md

# -mtime 3 ( 4 일째 되는 파일 )
# exit_status.md 파일을 포함해서 날짜가 Jun 8 인 파일들이 출력된다.
$ find * -daystart -type f -mtime 3 -ls
6572535  8 -rw-rw-r-- 1 mug896 mug896  7212 Jun 8 12:35 exit_status.md
6570224  8 -rw-rw-r-- 1 mug896 mug896  7430 Jun 8 18:25 keyword_commands.md
6570196 12 -rw-rw-r-- 1 mug896 mug896 10343 Jun 8 18:20 test.md

size 테스트와 +n, -n, n 숫자 값의 사용

  • +n 은 greater than n
  • -n 은 less than n
  • n 은 exactly n
Suffix Description
b for 512-byte blocks (suffix 를 사용하지 않을 경우 default 값)
c for bytes
w for two-byte words
k for Kibibytes (KiB, units of 1024 bytes)
M for Mebibytes (MiB, units of 1024 * 1024 = 1048576 bytes)
G for Gibibytes (GiB, units of 1024 * 1024 * 1024 = 1073741824 bytes)

greater than

# greater than 1 Kibibytes
$ find /bin -size +1k -exec stat -c "%s %n" {} + | sort -n | head -3
1297 /bin/bzmore
1659 /bin/cgroups-mount
1777 /bin/zcmp

# greater than 2 Kibibytes
$ find /bin -size +2k -exec stat -c "%s %n" {} + | sort -n | head -3
2131 /bin/zforce
2140 /bin/bzdiff
2301 /bin/gunzip

# greater than 1 Mebibytes
$ find /usr/bin -size +1M -exec stat -c "%s %n" {} + | sort -n | head -3
1052728 /usr/bin/pidgin
1094560 /usr/bin/gnome-keyring-daemon
1099184 /usr/bin/sqldiff

# greater than 2 Mebibytes
$ find /usr/bin -size +2M -exec stat -c "%s %n" {} + | sort -n | head -3
2248304 /usr/bin/hte
2283472 /usr/bin/qemu-arm
2291664 /usr/bin/qemu-armeb

less than

less than 은 사용 시 주의할 점이 하나 있는데 먼저 숫자 값에서 -1 을 해야 한다는 것입니다. 그러니까 1M 보다 작은 크기의 파일은 -1M 가 아니라 -2M 로 적어야 되고 5k 보다 작은 파일은 -5k 가 아니라 -6k 로 적어야 합니다.

# less than 0 Kibibytes 이 되므로 아무것도 표시되지 않는다.
$ find /bin -size -1k -exec stat -c "%s %n" {} + | sort -rn | head -3
$

# less than 1 Kibibytes
$ find /bin -size -2k -exec stat -c "%s %n" {} + | sort -rn | head -3 
946 /bin/which
435 /bin/cgroups-umount
140 /bin/zfgrep

# less than 2 Kibibytes
$ find /bin -size -3k -exec stat -c "%s %n" {} + | sort -rn | head -3 
2037 /bin/zless
1937 /bin/zcat
1910 /bin/zmore

# less than 0 Mebibytes 이 되므로 아무것도 표시되지 않는다.
$ find /bin -size -1M -exec stat -c "%s %n" {} + | sort -rn | head -3
$

# less than 1 Mebibytes
$ find /bin -size -2M -exec stat -c "%s %n" {} + | sort -rn | head -3
748968 /bin/brltty
584072 /bin/udevadm
554104 /bin/ip

# less than 2 Mebibytes
$ find /bin -size -3M -exec stat -c "%s %n" {} + | sort -rn | head -3
2022480 /bin/busybox
1113504 /bin/bash
748968 /bin/brltty

exact

suffix 를 사용하지 않을 경우 default 는 b 이므로 exact 사이즈는 c suffix 를 사용합니다.

$ find /bin -size 101560c -ls
393495  100 -rwxr-xr-x   1 root  root   101560 Apr 28  2017 /bin/gzip