find 명령 정리.

그동안 find 에 관한 글을 많이 썼다.
그런데도 아직까지 쓸 때마다 헷갈린다. 자주 쓰질 않기 때문이고, 늙어서 익힌 거라 자꾸 잊어버리기 때문이기도 할터인데..

아무튼, 여기 또 한번 정리해본다.
일단, 그간 썼던 걸 모아본다.

아래 두가지는 find 인듯 하지만, 사실은 find 와 관련이 없는 글들이다.

다음 글들이 Find 와 관련된 글들이다.


자.. 제대로 정리를 해보자.

find 기본 사용법은 넘어간다. 그 정도는 알고 있으므로.
그러나, 다음 명령어가 어떻게 다른지 구분할 수 있을까?

$ find ~ | wc -l
$ find ~ -exec wc -l {} +

wc -l 명령은, standard out 을 받아서, 개행문자(\n)가 몇 개인지를 세준다. 따라서, find ~ | wc -l명령은, 홈디렉토리 이하 찾은 모든 파일목록이 wc -l 의 인수가 된다.

반면, find ~ -exec wc -l {} + 는 완전히 다르다.
-exec 를 주면, find 로 찾은 파일 하나 하나의 경로명(pathname)이 -exec 이하의 {} 로 대입된다.
예를 들어, 처음 찾은 파일이 ~/abc.txt 였다면, ~/abc.txt 가 {} 에 대입이 된다.
따라서, 위 두가지 명령은 전혀 다른 결과를 낳게 된다.
첫번째는, find 로 찾은 모든 파일목록이 | 이후 명령의 인수로 넘어가게 되고(인수는 생략된 형태), 두번째는 찾은 파일 하나하나 경로명이 {} 에 대입된다.

그리고, 예전에는 {} + 대신 {} ;({} \;)을 사용했다. 지금도 이렇게 많이 쓰고는 있는데, 이건 -exec 뒤 명령어가 find 수만큼 실행되는 문제가 있다.

예를 들어, 다음 두 명령은 효율성에서 차이가 있다.

$ find ~ -type f -name 'foo*' -exec ls -l {} \;
$ find ~ -type f -name 'foo*' -exec ls -l {} +

위 명령은 ls 가 n 회(find 결과 수만큼) 실행되지만, 아래 명령은 1회만 실행된다.
그런데, 두 방법이 결과에 있어 차이가 날 때가 있다.

먼저, 같은 결과를 보여주는 경우를 생각해보자.

$ find . -type f \( -iname "*.cpp" -o -iname "*.md" \)  -exec ls -l '{}' \; 
-rw-rw-r-- 1 nemo nemo 8520  1월 27  2015 ./README.md
-rw-rw-r-- 1 nemo nemo 6169  1월 27  2015 ./src/avatar.cpp
-rw-rw-r-- 1 nemo nemo 13359  1월 27  2015 ./src/game.cpp
-rw-rw-r-- 1 nemo nemo 1524  1월 27  2015 ./src/ghost1.cpp
-rw-rw-r-- 1 nemo nemo 285  1월 27  2015 ./src/globals.cpp
-rw-rw-r-- 1 nemo nemo 3912  1월 27  2015 ./src/helperFns.cpp

$ find . -type f \( -iname "*.cpp" -o -iname "*.md" \)  -exec ls -l '{}' +
-rw-rw-r-- 1 nemo nemo  8520  1월 27  2015 ./README.md
-rw-rw-r-- 1 nemo nemo  6169  1월 27  2015 ./src/avatar.cpp
-rw-rw-r-- 1 nemo nemo 13359  1월 27  2015 ./src/game.cpp
-rw-rw-r-- 1 nemo nemo  1524  1월 27  2015 ./src/ghost1.cpp
-rw-rw-r-- 1 nemo nemo   285  1월 27  2015 ./src/globals.cpp
-rw-rw-r-- 1 nemo nemo  3912  1월 27  2015 ./src/helperFns.cpp

결과는 같지만, 정렬 상태는 미묘하게 다르다. 왜 그런지까지는..

하지만, 다음 명령은 완전히 다른 결과를 보여준다.

$ find . -type f \( -iname "*.cpp" -o -iname "*.md" \)  -exec grep 'vim' '{}' \;
PacVim is a game that teaches you vim commands.
I did not find a fun, free way to learn about the vim commands
practice with the vim commands while being a ton of fun to play.
$ pacvim
$ pacvim LEVEL_NUMBER
1. You cannot move into the walls (yellow color).  You must use vim motions to jump over them.
Winning conditions: Use vim commands to move the cursor
the /usr/local/share/pacvim-maps folder. After installing, you may, instead, use the *maps* folder (where you installed
for the "w" (or "W" if true) vim command.
	// EG: ./pacvim 4 --> player starts on 4th level
			cout << "\nInvalid arguments. Try ./pacvim or ./pacvim #" <<
				"\nEG: ./pacvim 8" << endl << endl;

$ find . -type f \( -iname "*.cpp" -o -iname "*.md" \)  -exec grep 'vim' '{}' +
./README.md:PacVim is a game that teaches you vim commands.
./README.md:I did not find a fun, free way to learn about the vim commands
./README.md:practice with the vim commands while being a ton of fun to play.
./README.md:$ pacvim
./README.md:$ pacvim LEVEL_NUMBER
./README.md:1. You cannot move into the walls (yellow color).  You must use vim motions to jump over them.
./README.md:Winning conditions: Use vim commands to move the cursor
./README.md:the /usr/local/share/pacvim-maps folder. After installing, you may, instead, use the *maps* folder (where you installed
./README.md:for the "w" (or "W" if true) vim command.
./src/game.cpp:	// EG: ./pacvim 4 --> player starts on 4th level
./src/game.cpp:			cout << "\nInvalid arguments. Try ./pacvim or ./pacvim #" <<
./src/game.cpp:				"\nEG: ./pacvim 8" << endl << endl;

+ 를 쓰면 원하는 문구가 들어있는 파일을 명시해준다.
이 방식이 그냥 grep 를 했을 때 결과와 유사하다. find 없이 grep 를 단독 실행했을 경우는, 색깔로 구분지어서 결과를 보여준다.

어떤 게 더 좋은 건지는 잘 모르겠으나..

하나만 기억하기로 하되, “세미콜론을 써야할 때도 있음”까지 기억하면 더 좋겠지.

find 경로 조건 -exec 외부명령 {} +

아래는 ‘지식’, 또는 ‘학습’을 위해 기록해둔다.

위 명령을 이렇게 쓸 수도 있다.

find -type f -iname "*.txt" | xargs ls -l

xargs 는 ‘공백’을 구분자로 사용하기 때문에, 위 명령은 오류 발생 소지가 있다. 파일명/디렉토리명에 공백이 들어가면 제대로 처리를 하지 못한다.
사고를 미연에 방지하기 위해선 다음과 같이 명령을 바꿔야 한다.

find -type f -iname "*.txt" | xargs -d '\n' ls -l

또는,

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

-print0 은 find manpage 에 이렇게 나와있다.

두가지 모두 명령 자체가 길고, 따라서 지저분하고, 결과로 외우기 쉽지 않다.
-exec {} + 가 깔끔하지는 않아도, 그나마 짧다.


다음은, 원하는 파일들을 모두 찾아서 그 파일 내부에 특정 문구가 있는 지를 찾는 방법에 대해 알아본다.

find . -type f -iname "*.*" -exec grep 'abcd' {} +

이런 형식으로, 먼저 find 로 특정 파일을 찾고, 그 파일 내용을 grep 를 사용해서 검색하여 ‘abcd’ 라는 문구가 포함되어 있는지 찾아낸다.
결과로, ‘abcd’ 가 들어있는 파일과, 해당 문구가 들어있는 행(行)을 반환해준다.


마지막으로, 원하는 파일들을 모두 찾아서 그 파일 내부에 있는 특정 문구를 다른 문구로 바꾸고 싶을 때 사용하는 방법. 바로 위 방법과 유사한데, 사용 명령어만 다르다.

find . -type f \( -iname "*.cpp" -o -iname "*.md" \)  -exec sed 's/vim/빔/g' {} +

find OR 연산의 예를 위해 이렇게도 넣어봤다.

위의 sed 는 실제로 파일 내부 문자열을 교체하지는 않는다. 대신 바꾼 결과를 화면으로만 출력한다.
실제 교체를 하려면 -i 를 붙여줘야 한다.

find . -type f \( -iname "*.cpp" -o -iname "*.md" \)  -exec sed -i 's/vim/빔/g' {} +

find 선택자도 알아두면 쓸만하다.

  • -cmin n : 파일 내용과 Attr. 가 n 분(分) 전에 고쳐졌을 경우만 찾음.
  • -mmin n : 파일 내용이 n 분(分) 전에 고쳐졌을 경우만 찾음.
  • -ctime n : cmin 과 같지만, 단위는 24 시간. 즉 n=2 이면 48시간.
  • -atime n : 단위는 시간. 몇 시간 전에 액세스 됐는지.
  • -mtime n : mmin 과 같지만, 단위는 24시간.
  • -perm mode : 해당 권한을 가진 파일만 찾음. 8진수/rwx 로 표현.

단! 여기서 시간 설정은 -n, +n 으로 써야 원하는 결과를 얻을 수 있다. -cmin 60 은 정확히 60분 전에 고쳐진 파일을 찾으라는 명령으로 보인다. 60분 이전을 원하면(대부분 그럴테지만, 이렇게 써줘야 한다.)

find ~/.config -type f -cmin -60

대략 이 정도면 원만한 find life 를 즐길 수 있을 듯 하다.

Tags:, ,

안녕하세요. 글 남겨주셔서 고맙습니다.