macOS 초보를 위한 터미널 사용 가이드 - Week 03
find 와 xargs
- 이전 문서: [[/mac/terminal-guide/02]]
- 다음 문서: [[/mac/terminal-guide/04]]
find 명령
엄청 오래된 find 명령
[[/cmd/find]]는 1971년에 나온 Version 1 AT&T UNIX부터 있었으므로 50년이 넘는 역사적인 명령이라 할 수 있습니다. [[/cmd/find#email-1995]]{C 언어의 창시자인 데니스 리치에 의하면} 벨 연구소에서 켄 톰슨과 데니스 리치가 UNIX를 만들고 있을 때, 옆 부서의 딕 하이트가 [[/cmd/find]]를 만들고 있었다고 합니다.
워낙 오래된 명령이다 보니 [[/cmd/find]]는 여러 차례 재개발되었고, 그 와중에 계속해서 기능이 추가되었으며, 결국 50년 넘게 살아남아 전 세계의 UNIX, Linux 에서 아직까지 흔하게 사용되는 애플리케이션이 되었습니다.
그래서 [[/cmd/find]]는 좀 이상한 면도 있고, 사용하기 어렵기도 하고… 만능 애플리케이션들이 다 그렇듯 옵션도 너무 많습니다. 그래서 그냥 오래된 물건이라 그러려니 하고 몇 가지 자주 쓰는 옵션들만 기억해두면 그럭저럭 잘 쓸 수 있습니다.
[[/cmd/find]]의 현대적인 대안으로 나름 이름을 얻고 있는 CLI 애플리케이션들도 몇 가지 나오긴 했습니다만, 그런 것들을 쓸 수 없는 환경도 있으니 [[/cmd/find]]의 기본적인 사용법을 익혀둬서 손해볼 것은 없습니다.
조회 연습
첫 [[/cmd/find]] 명령을 입력해 봅시다.
다음과 같이 특정 경로를 지정하면 해당 경로의 모든 파일과, 그 하위의 모든 파일을 재귀적으로 탐색해 출력합니다.
find ~/Downloads
find ~/Desktop
-type 옵션
--type
옵션을 사용하면 파일 타입으로 조회 결과를 필터링할 수 있습니다.
# 일반 파일만 조회한다
find ~/Downloads -type f
-type f
: 일반 파일-type d
: 디렉토리-type l
: 심볼릭 링크
f
, d
는 자주 사용하기 때문에 외워두면 좋습니다.
f
, d
, l
외에도 b
, c
, s
등의 다른 타입 옵션이 있긴 하지만 시스템 디바이스를 검색할 일이 드물기 때문에 굳이 외울 필요는 없습니다.
…하지만 궁금하니까 b
를 지정해서 bloc special 파일들만 찾아 봅시다. 이런 것도 해보면 경험이 되겠죠.
$ find /dev -type b 2>/dev/null | head
/dev/disk0
/dev/disk0s1
/dev/disk0s3
/dev/disk0s2
/dev/disk1
/dev/disk1s2
/dev/disk1s1
/dev/disk1s3
/dev/disk2
/dev/disk1s4
find /dev
: 디바이스 디렉토리에서-type b
: 블록 특수 파일을 찾아라2>/dev/null
: 에러 메시지는/dev/null
로 보내서 스크린에 출력되지 않게 한다| head
: 결과를 첫 10줄만 출력한다
스크린샷 파일들은 기본적으로 ~/Desktop
에 생성 날짜를 이름으로 해서 저장되니 예제로 적절할 것 같습니다.
이번에는 -type d
를 사용해 ~/Desktop
디렉토리의 모든 디렉토리를 찾아봅시다.
$ find ~/Desktop -type d
/Users/johngrib/Desktop
/Users/johngrib/Desktop/2023
/Users/johngrib/Desktop/2024
이번에는 나의 ~/Desktop
디렉토리의 모든 하위 경로에 파일이 몇 개나 있는지 알아봅시다.
$ find ~/Desktop -type f | wc -l
53
53개의 파일이 있네요.
find ~/Desktop
:~/Desktop
디렉토리에서 찾아라-type f
: 파일만 찾아라| wc -l
: 출력된 결과가 몇 줄인지 출력해라
-name 과 -iname 옵션
53개는 좀 많으니까 필터링 조건을 추가해 봅시다.
-name
을 써서 파일 이름의 확장자가 jpg
인 것들만 골라보죠.
$ find ~/Desktop -type f -name "*.jpg"
/Users/johngrib/Desktop/2024/screenshot 2024-01-19 22.12.50.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-20 18.31.12.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-09 22.26.44.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-20 16.39.12.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-14 20.53.31.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.20.27.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-23 20.49.58 복사본.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-09-15 17.35.20.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 22.00.49.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-18 22.34.18.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-19 19.40.48.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 22.17.22.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.22.43.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 21.49.08.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-19 19.40.05.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 21.49.16.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-21 23.34.05.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 22.02.26.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-22 22.00.23.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.19.47.jpg
/Users/johngrib/Desktop/2023/IMG_9216.jpg
$ find ~/Desktop -type f -name "*.jpg" | wc -l
21
find ~/Desktop
:~/Desktop
디렉토리에서 찾아라-type f
: 파일만 찾아라-name "*.jpg"
: 파일 확장자가jpg
인 파일만 찾아라| wc -l
: 결과가 몇 줄인지 출력해라
[[/cmd/wc]]로 한번 더 확인해보니 21개의 파일이 있었네요.
그런데 생각해보니 jpg
말고도 JPG
파일도 있을 것 같습니다.
-name
옵션은 대소문자를 구분합니다. 그래서 JPG
는 나오지 않았을 것 같습니다.
대소문자를 구별하지 않는 -iname
옵션을 사용해봅시다.
$ find ~/Desktop -type f -iname "*.jpg"
/Users/johngrib/Desktop/2024/screenshot 2024-01-19 22.12.50.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-15 22.43.45.JPG
/Users/johngrib/Desktop/2024/screenshot 2024-01-20 18.31.12.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-20 16.39.12(2).JPG
/Users/johngrib/Desktop/2024/screenshot 2024-01-09 22.26.44.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-20 16.39.12.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-14 20.53.31.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.20.27.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-23 20.49.58 복사본.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-09-15 17.35.20.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 22.00.49.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-18 22.34.18.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-19 19.40.48.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 22.17.22.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.22.43.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 21.49.08.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-19 19.40.05.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 21.49.16.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-21 23.34.05.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-17 22.02.26.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-22 22.00.23.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.19.47.jpg
/Users/johngrib/Desktop/2023/IMG_9216.jpg
$ find ~/Desktop -type f -iname "*.jpg" | wc -l
23
find ~/Desktop
:~/Desktop
디렉토리에서 찾아라-type f
: 파일만 찾아라-iname "*.jpg"
: 대소문자를 무시하고, 확장자가jpg
인 파일만 찾아라| wc -l
: 결과가 몇 줄인지 출력해라
JPG
파일이 2개가 더 나왔습니다.
-newermt
이번에는 -newermt
옵션을 사용해 봅시다.
이 옵션을 쓰면 파일의 업데이트 메타데이터를 체크해서, 지정한 날짜 이후에 업데이트된 파일을 찾아줍니다.
$ find ~/Desktop -type f -iname '*.jpg' -newermt '2023-12-25'
/Users/johngrib/Desktop/2024/screenshot 2024-01-19 22.12.50.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-15 22.43.45.JPG
/Users/johngrib/Desktop/2024/screenshot 2024-01-20 18.31.12.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-20 16.39.12(2).JPG
/Users/johngrib/Desktop/2024/screenshot 2024-01-09 22.26.44.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-20 16.39.12.jpg
/Users/johngrib/Desktop/2024/screenshot 2024-01-14 20.53.31.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.20.27.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-23 20.49.58 복사본.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.22.43.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.19.47.jpg
/Users/johngrib/Desktop/2023/IMG_9216.jpg
find ~/Desktop
:~/Desktop
디렉토리에서 찾아라-type f
: 파일만 찾아라-iname '*.jpg'
: 파일 이름이*.jpg
인 파일만 찾도록 하고, 대소문자는 무시한다-newermt '2023-12-25'
: 2023년 12월 25일 이후에 업데이트된 파일만 찾아라
조건을 부정하는 !
이제 조건에 NOT 조건을 씌우는 방법도 사용해 봅시다.
그냥 !
를 쓰면 됩니다.
$ find ~/Desktop -type f -iname '*.jpg' -newermt '2023-12-25' ! -newermt '2024-01-01'
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.20.27.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-23 20.49.58 복사본.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.22.43.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.19.47.jpg
/Users/johngrib/Desktop/2023/IMG_9216.jpg
find ~/Desktop
:~/Desktop
디렉토리에서 찾아라-type f
: 파일만 찾아라-iname '*.jpg'
: 파일 이름이*.jpg
인 파일만 찾도록 하고, 대소문자는 무시한다-newermt '2023-12-25'
: 2023년 12월 25일 이후에 업데이트된 파일만 찾아라! -newermt '2024-01-01'
: 2024년 1월 1일 이전에 업데이트된 파일만 찾아라
[[/cmd/find]]는 이 외에도 다양한 검색 옵션들을 갖고 있습니다.
옵션에 따라 파일의 용량별, 시간별, 타입별로 다양한 방법의 조회가 가능합니다. 너무 많아서 여기서는 더 이상의 검색 옵션은 다루지 않도록 하겠습니다.
자세한 내용은 man find
명령어로 확인해 보거나, ChatGPT에게 물어보세요.
[[/cmd/find]]가 수십년이나 된 명령이다 보니 ChatGPT도 이 명령을 아주 잘 알고 있더군요.
man find
조회 결과에 명령을 실행해주는 -exec
[[/cmd/find]]를 사용한 조회는 이제 충분히 익숙해졌다 치고, 다른 것을 해봅시다.
echo로 연습
[[/cmd/find#option-exec]]{-exec} 옵션을 사용하면 [[/cmd/find]]명령으로 찾은 결과에 대해 적용할 명령을 지정할 수 있습니다.
명령이 좀 길어질테니 \
를 사용해서 명령을 여러 줄로 나눠서 써봅시다.
find ~/Desktop -type f -iname '*.jpg' \
-newermt '2023-12-25' \
! -newermt '2024-01-01' \
-exec echo {} \;
위의 명령을 실행해보니 다음과 같이 출력되는군요. (둘째줄부터 나타나는 >
는 입력칸이라고 알려주는 프롬프트이니 따라치지 마세요.)
$ find ~/Desktop -type f -iname '*.jpg' \
> -newermt '2023-12-25' \
> ! -newermt '2024-01-01' \
> -exec echo {} \;
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.20.27.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-11-23 20.49.58 복사본.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.22.43.jpg
/Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.19.47.jpg
/Users/johngrib/Desktop/2023/IMG_9216.jpg
find ~/Desktop
:~/Desktop
디렉토리에서 찾아라-type f
: 파일만 찾아라-iname '*.jpg'
: 파일 이름이*.jpg
인 파일만 찾도록 하고, 대소문자는 무시한다-newermt '2023-12-25'
: 2023년 12월 25일 이후에 업데이트된 파일만 찾아라! -newermt '2024-01-01'
: 2024년 1월 1일 이전에 업데이트된 파일만 찾아라-exec echo {} \;
: 찾은 파일에 하나하나에 대해echo {}
명령을 실행해라{}
는 placeholder 입니다. 즉, 찾은 파일의 이름이 여기에 들어갑니다.\;
는-exec
옵션의 끝을 알리는 표시입니다.
[[/cmd/echo]]는 그냥 출력만 하니까, 사실 위의 명령 파이프라인에서는 -exec echo {} \;
는 불필요하게 덧붙인 옵션이라고 할 수 있습니다.
wc, du로 연습
하지만 다음과 같이 [[/cmd/wc]]를 사용한다면 byte 사이즈를 알 수 있을 것입니다.
$ find ~/Desktop -type f -iname '*.jpg' \
> -newermt '2023-12-25' \
> ! -newermt '2024-01-01' \
> -exec wc -c {} \; \
> | sort -n
191081 /Users/johngrib/Desktop/2023/IMG_9216.jpg
220545 /Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.20.27.jpg
250355 /Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.19.47.jpg
425308 /Users/johngrib/Desktop/2023/screenshot 2023-11-23 20.49.58 복사본.jpg
1140371 /Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.22.43.jpg
-exec wc -c {} \;
: 찾은 파일에 하나하나에 대해wc -c {}
명령을 실행해 용량을 출력해라| sort -n
: [[/cmd/sort]] 명령를 사용해 오름차순으로 정렬해라
지정한 날짜 범위 내에서 어떤 jpg 파일이 제일 용량이 큰 지를 조사한 셈이 되었습니다.
물론 [[/cmd/du]]를 써서 사람이 읽기 좋은 용량 사이즈를 표시해보는 것도 괜찮은 연습이 되겠네요.
$ find ~/Desktop -type f -iname '*.jpg' \
> -newermt '2023-12-25' \
> ! -newermt '2024-01-01' \
> -exec du -h {} \;
216K /Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.20.27.jpg
416K /Users/johngrib/Desktop/2023/screenshot 2023-11-23 20.49.58 복사본.jpg
1.1M /Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.22.43.jpg
248K /Users/johngrib/Desktop/2023/screenshot 2023-12-27 21.19.47.jpg
188K /Users/johngrib/Desktop/2023/IMG_9216.jpg
cp로 연습
이번에는 이렇게 조회한 파일들을 ~/test
경로로 복사해보겠습니다.
먼저 ~/test
디렉토리를 만들어 줍시다.
mkdir ~/test
그리고 [[/cmd/find]]의 -exec
옵션에 파일을 복사하는 cp
명령을 써서 파일을 복사해봅시다.
$ find ~/Desktop -type f -iname '*.jpg' \
> -newermt '2023-12-25' \
> ! -newermt '2024-01-01' \
> -exec cp {} ~/test \;
-exec cp {} ~/test \;
: 찾은 파일에 하나하나에 대해cp {} ~/test
명령을 실행해라
이제 ~/test
디렉토리를 확인해 보면 복사된 파일들이 있습니다.
$ ls -alh ~/test
total 2.2M
drwxr-xr-x 8 johngrib staff 256 2024-01-21 Sun 17:50:22 .
drwxr-x---+ 84 johngrib staff 2.7K 2024-01-21 Sun 17:46:14 ..
-rw-r--r-- 1 johngrib staff 187K 2024-01-21 Sun 17:50:22 IMG_9216.jpg
-rw-r--r-- 1 johngrib staff 416K 2024-01-21 Sun 17:50:22 'screenshot 2023-11-23 20.49.58 복사본.jpg'
-rw-r--r-- 1 johngrib staff 245K 2024-01-21 Sun 17:50:22 'screenshot 2023-12-27 21.19.47.jpg'
-rw-r--r-- 1 johngrib staff 216K 2024-01-21 Sun 17:50:22 'screenshot 2023-12-27 21.20.27.jpg'
-rw-r--r-- 1 johngrib staff 1.1M 2024-01-21 Sun 17:50:22 'screenshot 2023-12-27 21.22.43.jpg'
-exec의 종결 표시자 ; 와 +
바로 위에서 -exec
옵션의 종결 표시자로 \;
를 사용했습니다.
그런데 이 옵션의 종결 표시자는 \;
만 있는 게 아니라 +
도 있습니다.
\;
: 찾은 파일 하나하나에 대해-exec
로 지정한 명령을 실행합니다.+
:-exec
로 지정한 명령 하나에 대해 찾은 파일을 모두 넘겨줍니다.
즉 \;
를 사용했을 때 검색된 파일이 100개라면 -exec
로 지정한 명령이 100번 실행되지만,
+
를 사용했을 때는 -exec
로 지정한 명령이 1번 실행되고, 찾은 파일 100개를 한꺼번에 넘겨줍니다.
다음 두 명령을 실행해보며 결과를 비교해 봅시다.
find ~/Desktop -type f -exec echo {} \;
find ~/Desktop -type f -exec echo {} +
만약 [[/cmd/find]]로 찾은 결과가 a
, b
, c
이렇게 세가지라 할 때,
-exec echo {} \;
를 사용하면 다음과 같이 3개의 명령을 연달아 실행한 것과 똑같습니다.
echo a
echo b
echo c
반면 -exec echo {} +
를 사용하면 다음과 같이 명령을 하나 실행한 것과 똑같습니다.
echo a b c
따라서 여러 파일을 한꺼번에 입력 인자로 받을 수 있는 명령이라면 \;
가 아니라 +
를 사용하면 더 나은 성능을 보이는 경우가 많습니다.
파일의 형식을 판별해주는 [[/cmd/file]]로 실험을 해봅시다.
find ~/Downloads -type f -exec file {} \;
find ~/Downloads -type f -exec file {} +
~/Downloads
에는 보통 많은 파일이 있기 때문에 위의 두 명령을 실행해봤을 때 체감상 차이를 느낄 수 있을 것입니다.
하지만 파일의 수가 적은 사람도 있을 수 있고, 느낌은 확실하지 않으니까 time
명령을 사용해서 시간을 측정해봅시다.
$ time find ~/Downloads -type f -exec file {} \; > /dev/null 2> /dev/null
real 0m0.147s
user 0m0.044s
sys 0m0.084s
$ time find ~/Downloads -type f -exec file + > /dev/null 2> /dev/null
real 0m0.007s
user 0m0.001s
sys 0m0.004s
\;
를 썼을 땐 0.147초가 걸렸지만 +
를 썼을 땐 0.007초가 걸렸습니다.
이번에는 소요시간만 보고 싶고,
[[/cmd/file]]가 출력한 결과는 관심이 없어서 표준출력과 표준에러출력을 /dev/null
로 리다이렉트했습니다.
한편 1, 2를 모두 /dev/null
로 리다이렉팅했는데도 측정한 시간이 출력되는 것을 보면
time
명령이 측정 결과를 [[/cmd/tty]]{/dev/tty}로 보내고 있음을 추측할 수 있습니다.
명령의 퍼포먼스를 체크하는 time
의 특성을 잘 생각해보면 [[/cmd/tty]]{/dev/tty}로 출력을 보내는 이유를 알 수 있습니다.
time
은 측정하려는 명령 파이프라인의 퍼포먼스를 측정하기만 해야 하고,
1번 출력이나 2번 출력으로 측정한 결과를 내보내서 해당 명령의 출력을 오염시키면 안 되기 때문입니다.
만약 time
이 1번 출력으로 측정 결과를 내보낸다면, 명령 파이프라인 마지막에 >
로 파일 리다이렉팅을 할 때 측정 결과가 파일에 저장되어 버릴 것입니다.
xargs 명령
[[/cmd/xargs]]는 '수직 리스트'를 '수평 리스트'로 바꿔주는 명령입니다. 단순한 기능을 갖고 있으면서도 매우 강력하고 유용합니다.
이 훌륭한 명령은 직접 써봐야 진가를 알 수 있습니다.
먼저 [[/cmd/seq]]를 써서 1부터 10까지의 숫자를 출력해보겠습니다.
$ seq 1 10
1
2
3
4
5
6
7
8
9
10
여기에 [[/cmd/xargs]]를 파이프로 연결해주면 수직 리스트가 수평 리스트로 바뀝니다.
$ seq 1 10 | xargs
1 2 3 4 5 6 7 8 9 10
[[/cmd/xargs#option-n]]{-n 옵션}을 주면 지정한 갯수만큼 출력한 다음, 아랫줄로 내려가서 다시 지정한 갯수만큼 출력합니다.
$ seq 1 10 | xargs -n 3
1 2 3
4 5 6
7 8 9
10
$ seq 1 10 | xargs -n 4
1 2 3 4
5 6 7 8
9 10
find 와 조합해 사용하기
이제 [[/cmd/find]] 명령을 써서 다음과 같이 10개의 html 파일을 출력했다고 합시다.
$ find ~/johngrib.github.io -name '*.html' | head
/Users/johngrib/johngrib.github.io/_includes/post-tags.html
/Users/johngrib/johngrib.github.io/_includes/createLink.html
/Users/johngrib/johngrib.github.io/_includes/latex.html
/Users/johngrib/johngrib.github.io/_includes/pagination.html
/Users/johngrib/johngrib.github.io/_includes/createTable.html
/Users/johngrib/johngrib.github.io/_includes/comment.html
/Users/johngrib/johngrib.github.io/_includes/footer.html
/Users/johngrib/johngrib.github.io/_includes/header.html
/Users/johngrib/johngrib.github.io/_includes/searchbox.html
/Users/johngrib/johngrib.github.io/index.html
만약 파일들 각각이 몇 줄로 되어있는지 조사하고 싶다면 어떻게 하면 좋을까요?
위에서 공부한 것과 같이 [[/cmd/find]]의 -exec
와 [[/cmd/wc]]를 응용하는 방법이 있을 것입니다.
$ find ~/johngrib.github.io -type f -name '*.html' -exec wc -l {} \; 2>/dev/null | head
3 /Users/johngrib/johngrib.github.io/_includes/post-tags.html
2 /Users/johngrib/johngrib.github.io/_includes/createLink.html
2 /Users/johngrib/johngrib.github.io/_includes/latex.html
96 /Users/johngrib/johngrib.github.io/_includes/pagination.html
61 /Users/johngrib/johngrib.github.io/_includes/createTable.html
20 /Users/johngrib/johngrib.github.io/_includes/comment.html
6 /Users/johngrib/johngrib.github.io/_includes/footer.html
107 /Users/johngrib/johngrib.github.io/_includes/header.html
5 /Users/johngrib/johngrib.github.io/_includes/searchbox.html
68 /Users/johngrib/johngrib.github.io/index.html
각 파일이 몇 줄로 되어 있는지를 조사하는 데 성공했습니다.
그런데 이걸 실행해보면 [[/cmd/find]]가 좀 느리다는 것을 실감할 수 있습니다.
-exec
때문입니다.
$ time find ~/johngrib.github.io -type f -name '*.html' 2>&1 >/dev/null | head
real 0m0.121s
user 0m0.014s
sys 0m0.107s
$ time find ~/johngrib.github.io -type f -name '*.html' -exec wc -l {} \; 2>&1 >/dev/null | head
real 0m1.477s
user 0m0.271s
sys 0m0.963s
-exec
를 쓰지 않았을 때에 비해 10배 이상 느려졌습니다.
-exec
를 쓰지 않고 라인 카운트를 할 방법을 생각해 봅시다.
이럴 때 사용하는 것이 바로 [[/cmd/xargs]]입니다.
$ find ~/johngrib.github.io -type f -name '*.html' | head | xargs wc -l
3 /Users/johngrib/johngrib.github.io/_includes/post-tags.html
2 /Users/johngrib/johngrib.github.io/_includes/createLink.html
2 /Users/johngrib/johngrib.github.io/_includes/latex.html
96 /Users/johngrib/johngrib.github.io/_includes/pagination.html
61 /Users/johngrib/johngrib.github.io/_includes/createTable.html
20 /Users/johngrib/johngrib.github.io/_includes/comment.html
6 /Users/johngrib/johngrib.github.io/_includes/footer.html
107 /Users/johngrib/johngrib.github.io/_includes/header.html
5 /Users/johngrib/johngrib.github.io/_includes/searchbox.html
68 /Users/johngrib/johngrib.github.io/index.html
370 total
똑같은 결과가 나왔지만… 속도는 훨씬 빠릅니다.
$ time find ~/johngrib.github.io -type f -name '*.html' | head | xargs wc -l
3 /Users/johngrib/johngrib.github.io/_includes/post-tags.html
2 /Users/johngrib/johngrib.github.io/_includes/createLink.html
2 /Users/johngrib/johngrib.github.io/_includes/latex.html
96 /Users/johngrib/johngrib.github.io/_includes/pagination.html
61 /Users/johngrib/johngrib.github.io/_includes/createTable.html
20 /Users/johngrib/johngrib.github.io/_includes/comment.html
6 /Users/johngrib/johngrib.github.io/_includes/footer.html
107 /Users/johngrib/johngrib.github.io/_includes/header.html
5 /Users/johngrib/johngrib.github.io/_includes/searchbox.html
68 /Users/johngrib/johngrib.github.io/index.html
370 total
real 0m0.037s
user 0m0.007s
sys 0m0.038s
[[/cmd/xargs]]는 표준입력으로 받은 목록을 [[/cmd/wc]]{wc -l} 오른쪽에 수평 리스트로 붙여준 것입니다.
-I 옵션과 placeholder
[[/cmd/xargs]]는 -I
옵션과 placeholder 기호를 통해 입력할 곳을 지정할 수 있습니다.
placeholder 기호는 보통 {}
를 사용합니다.
다른 것을 쓸 수도 있는데, 대부분이 {}
를 씁니다.
그래서 같이 일하는 다른 사람들을 헷갈리지 않게 하고 싶다면 그냥 {}
를 쓰는 것이 좋습니다.
$ seq 10 | xargs -I {} echo hello, {} ...
hello, 1 ...
hello, 2 ...
hello, 3 ...
hello, 4 ...
hello, 5 ...
hello, 6 ...
hello, 7 ...
hello, 8 ...
hello, 9 ...
hello, 10 ...
$ seq 10 | xargs -I {} echo {} is {} ...
1 is 1 ...
2 is 2 ...
3 is 3 ...
4 is 4 ...
5 is 5 ...
6 is 6 ...
7 is 7 ...
8 is 8 ...
9 is 9 ...
10 is 10 ...
seq 10
: 1부터 10까지의 숫자를 출력합니다.xargs -I {}
: placeholder로{}
를 사용하겠다고 선언합니다.echo hello, {} ...
: [[/cmd/echo]]를 구성합니다. placeholder가 1개 있어서 인자가 한 번 입력됩니다.- 출력된 부분을 강조하기 위해
...
를 붙였습니다.
- 출력된 부분을 강조하기 위해
echo {} is {} ...
: [[/cmd/echo]]를 구성합니다. placeholder가 2개 있어서 인자가 두 번 입력됩니다.
물론 {}
말고 다른 걸 써도 됩니다.
$ seq 3 | xargs -I @ echo ... @ ...
... 1 ...
... 2 ...
... 3 ...
@
를 써봤습니다.
$ seq 3 | xargs -I number echo ... number ...
... 1 ...
... 2 ...
... 3 ...
문자 한 개가 아니라 여러개여도 된다는 것을 보여주기 위해 number
를 써봤습니다.
하지만 역시 {}
가 좀 덜 헷갈리네요. 매일 {}
로 써서 그런 것 같습니다.
-P 옵션으로 병렬 처리하기
[[/cmd/xargs]]의 훌륭한 점은 -P
옵션으로 병렬 처리를 할 수 있다는 것입니다.
물론 병렬처리가 늘 그렇듯이 순서는 보장되지 않습니다.
$ seq 10 | xargs -P4 -I {} echo hello, {} ...
hello, 1 ...
hello, 2 ...
hello, 4 ...
hello, 3 ...
hello, 5 ...
hello, 6 ...
hello, 7 ...
hello, 8 ...
hello, 9 ...
hello, 10 ...
seq 10
: 1부터 10까지의 숫자를 출력합니다.xargs -P4
: 4개의 프로세스를 사용해서 병렬 처리합니다.xargs -I {}
: placeholder로{}
를 사용하겠다고 선언합니다.echo hello, {} ...
: [[/cmd/echo]] 출력문을 구성합니다.
-P
옵션은 병렬 처리할 프로세스의 개수를 지정합니다.
-P4
는 4개의 프로세스를 사용해서 병렬 처리하겠다는 뜻입니다.
-P4
로 붙여서 써도 되고 -P 4
처럼 띄어서 써도 됩니다.
그리고 -P0
과 같이 0
을 지정하면 사용 가능한 최대 프로세스 개수를 사용합니다.
다음은 -P4
를 사용한 경우와 -P
를 사용하지 않은 경우의 퍼포먼스를 time
명령어로 비교한 것입니다.
$ time seq 1000 | xargs -P 4 -I {} echo hello, {} ... > /dev/null
real 0m0.407s
user 0m0.165s
sys 0m0.733s
$ time seq 1000 | xargs -I {} echo hello, {} ... > /dev/null
real 0m0.953s
user 0m0.143s
sys 0m0.732s
real
을 보면 -P4
가 대충 두 배 이상 빨랐네요.
-p 옵션으로 confirm 을 받도록 하기
[[/cmd/xargs]]에서 정말 유용합 옵션인 -p
옵션을 소개하고 xargs
에 대한 공부를 마무리하도록 하겠습니다.
-p
옵션을 쓰면 명령을 실행하기 전에 confirm을 받도록 할 수 있습니다.
프롬프트가 뜨면 y
나 n
을 입력해서 확인을 해주면 됩니다.
다음은 2
에 대해서만 y
를 입력하고, 나머지는 모두 n
을 입력한 경우입니다.
$ seq 3 | xargs -p -I {} echo hello, {} ...
echo hello, 1 ...?...n
echo hello, 2 ...?...y
hello, 2 ...
echo hello, 3 ...?...n
hello, 2...
만 출력됐습니다.
confirm을 받는 문구 때문에 좀 헷갈리니까, 표준출력을 파일로 리다이렉팅해서 확인해보겠습니다.
$ seq 3 | xargs -p -I {} echo hello, {} ... > result.txt
echo hello, 1 ...?...n
echo hello, 2 ...?...y
echo hello, 3 ...?...n
$ cat result.txt
hello, 2 ...
결과에는 hello, 2 ...
만 출력됐습니다.
이전, 다음 문서
- 이전 문서: [[/mac/terminal-guide/02]]
- 다음 문서: [[/mac/terminal-guide/04]]