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 에 이렇게 나와있다.

True; print the full file name on the standard output, followed by a null character (instead of the newline character that
-print uses). This allows file names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output. This option corresponds to the -0 option of xargs.

두가지 모두 명령 자체가 길고, 따라서 지저분하고, 결과로 외우기 쉽지 않다.
-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 를 즐길 수 있을 듯 하다.

Author: 아무도안

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