리눅스 xargs 명령어 사용법 - find 조합, -I 옵션, 병렬 처리 실전 예제
이 명령어를 언제 쓰는가
대부분의 리눅스 명령은 처리할 대상을 인자로 받는다. 그런데 find, grep -l, cat 같은 명령의 결과는 표준 출력이다. 이 둘 사이를 이어 주는 것이 xargs 다.
find . -name '*.tmp' | rm # 동작하지 않는다. rm 은 표준 입력을 안 읽는다
find . -name '*.tmp' | xargs rm # 동작한다
실무에서는 대량 파일 정리, 목록 기반 일괄 처리, 병렬 다운로드에 쓴다. -P 옵션으로 손쉽게 병렬 실행이 되기 때문에 스크립트를 따로 짜지 않고 처리량을 몇 배 올릴 수 있다.
기본 형식
다른명령 | xargs [옵션] 실행할명령 [고정인자]
xargs 는 표준 입력을 공백과 개행으로 쪼개서 실행할명령 뒤에 붙여서 실행한다. 명령을 생략하면 echo 가 기본값이라, 무엇이 넘어가는지 확인할 때 그냥 | xargs 만 쳐 봐도 된다.
자주 쓰는 옵션
| 옵션 | 의미 | 예시 |
|---|---|---|
-0 | 구분자를 널 문자로. 공백 든 파일명 대비 필수 | find . -print0 | xargs -0 rm |
-n N | 한 번에 인자 N개씩만 넘겨 실행한다 | xargs -n 1 echo |
-I {} | 인자를 {} 자리에 끼워 넣는다. 한 줄에 하나씩 실행 | xargs -I {} mv {} /backup/ |
-P N | N개를 동시에 병렬 실행한다 | xargs -P 4 -n 1 curl -O |
-t | 실행할 명령을 표준 오류로 찍어 준다. 확인용 | xargs -t rm |
-r | 입력이 비면 명령을 아예 실행하지 않는다 (GNU) | xargs -r rm |
-d 문자 | 구분자를 직접 지정한다 (GNU) | xargs -d '\n' ls -l |
실전 예제
1) 특정 확장자 파일 안에서 문자열을 검색한다. find 와 grep 을 잇는 가장 흔한 조합이다.
find . -type f -name '*.py' -print0 | xargs -0 grep -n 'DEBUG'
2) 오래된 임시 파일을 안전하게 지운다. 실행 전에 -t 로 무엇이 지워질지 확인한다.
find /tmp -type f -mtime +7 -print0 | xargs -0 -t -r rm
3) 파일 목록을 하나씩 다른 위치로 옮긴다. 인자가 명령 중간에 들어가야 하므로 -I 가 필요하다.
find /uploads -name '*.jpg' -print0 | xargs -0 -I {} mv {} /archive/images/
xargs 는 받은 인자를 명령 뒤에 붙인다. 그래서 -I 없이 xargs mv /archive/images/ 라고 쓰면 실제로 실행되는 명령은 mv /archive/images/ 파일1 파일2 다. mv 는 마지막 인자를 목적지로 보므로 디렉터리와 파일1 을 파일2 로 옮기려다 실패하고, 파일이 하나뿐이면 그 파일을 덮어써 버린다. 목적지가 뒤에 와야 하는 mv, cp 에서는 -I 를 잊지 않는다. GNU 환경이라면 xargs -0 mv -t /archive/images/ 처럼 -t 로 목적지를 앞으로 빼는 방법이 더 빠르다(파일마다 프로세스를 새로 띄우지 않는다).
4) URL 목록을 병렬로 내려받는다. 순차 다운로드보다 훨씬 빠르다.
cat urls.txt | xargs -P 8 -n 1 curl -sSfLO
5) 패키지 목록 파일로 일괄 설치한다. 인자를 모아서 한 번에 넘기므로 apt 가 의존성을 한꺼번에 푼다.
xargs -a packages.txt sudo apt-get install -y
파일에서 인자를 읽는 -a 는 GNU 확장이라 macOS·BSD 의 xargs 에는 없다. 그쪽에서는 xargs sudo ... < packages.txt 처럼 리다이렉션으로 받는다.
6) 실행 중인 특정 프로세스를 모두 종료한다. 목록을 먼저 눈으로 확인한 다음 kill 로 넘긴다.
pgrep -f 'worker.py' # 먼저 확인
pgrep -f 'worker.py' | xargs -r kill -TERM # 확인한 뒤 실행
함정과 주의점
공백이 든 파일명에서 엉뚱한 파일이 지워진다. xargs 는 기본적으로 공백도 구분자로 본다. my report.log 라는 파일 하나가 my 와 report.log 두 개의 인자로 쪼개진다. 삭제 명령이라면 실제 피해가 난다. 해결은 널 구분자다. find 에는 -print0, xargs 에는 -0 을 항상 짝으로 쓴다고 외운다.
find . -name '*.log' -print0 | xargs -0 rm
입력이 find 가 아니라 다른 명령이면 GNU 환경에서 xargs -d '\n' 으로 개행만 구분자로 삼는다.
입력이 비어도 명령이 한 번 실행된다. GNU xargs 는 인자가 하나도 없어도 명령을 인자 없이 한 번 실행한다. 사고가 나는 쪽은 인자가 없으면 현재 디렉터리나 표준 입력을 대상으로 삼는 명령이다.
find . -name '*.bak' | xargs ls -l # 결과가 비면 현재 디렉터리 목록이 나온다
find . -name '*.bak' | xargs du -sh # 결과가 비면 현재 디렉터리 용량을 잰다
find . -name '*.bak' | xargs grep ERROR # 결과가 비면 표준 입력을 기다리며 멈춘다
흔히 "결과가 비면 xargs rm -rf 가 현재 디렉터리를 통째로 지운다"고 하는데 그것은 사실이 아니다. 인자 없는 rm -rf 는 지울 대상이 없어 아무 일도 하지 않고 끝난다(-f 때문에 오류 메시지조차 안 난다). 그래도 빈 입력으로 명령을 헛돌리면 위처럼 엉뚱한 대상이 잡히거나 파이프라인이 멈추므로 -r(--no-run-if-empty) 을 붙이는 편이 낫다. macOS·BSD 의 xargs 는 기본 동작이 이미 "비면 실행 안 함"이라 -r 이 있으나 마나다(구버전에는 옵션 자체가 없어 오류가 났고, 최근 macOS 는 호환용으로 받아들이기만 한다). 그래서 맥에서 테스트한 스크립트가 리눅스 서버에서 사고를 내는 전형적인 케이스다. 리눅스용 스크립트에는 반드시 -r 을 넣는다.
인자가 많으면 명령이 여러 번 나뉘어 실행된다. 커널의 인자 길이 제한(ARG_MAX) 때문에 xargs 는 목록을 알아서 쪼개 명령을 두 번, 세 번 실행한다. rm 이나 grep 은 문제없지만 tar cf out.tar 처럼 두 번째 실행이 첫 번째 결과를 덮어쓰는 명령에서는 데이터가 날아간다. 이런 명령에는 tar -T 목록파일 처럼 목록 파일을 받는 기능을 쓴다.
-I 를 쓰면 인자를 하나씩만 넘긴다. -I {} 는 -L 1 과 같아서 입력 한 줄마다 프로세스를 새로 띄운다. 파일이 10만 개면 10만 번이다. 명령이 인자를 여러 개 받을 수 있다면 -I 없이 쓰는 편이 수십 배 빠르다.
파이프 안에서 sudo 를 쓰면 비밀번호를 못 받는다. xargs 가 표준 입력을 이미 점유하고 있어서 sudo 가 프롬프트를 띄우지 못한다. 미리 sudo -v 로 인증을 받아 두거나 NOPASSWD 설정을 쓴다.
함께 보면 좋은 명령어
- find -exec — 대상이 find 결과뿐이라면
-exec ... +로 파이프 없이 끝낼 수 있다. - parallel (GNU parallel) — 병렬 처리가 본격적으로 필요하면 진행률·재시도까지 지원하는 이쪽이 낫다.
- while read — 인자로 넘기기 어려운 복잡한 처리는
while IFS= read -r line루프로 푼다.