xargs는 표준 입력으로 들어온 텍스트를 잘라서 다른 명령의 인자로 붙여 실행해 주는 도구입니다. find ... | xargs rm처럼 "앞 명령이 뱉은 목록을 뒷 명령에 먹이는" 다리 역할을 하죠. 파이프(|)는 출력을 다음 명령의 입력으로 넘기지만, xargs는 그걸 인자로 바꿔준다는 게 핵심 차이입니다.
이 차이를 모르면 echo *.log | rm 같은 걸 쓰다 "왜 안 지워지지?" 하고 헤맵니다. rm은 표준 입력을 읽지 않거든요. 파일 이름은 인자로 줘야 합니다. 그 변환이 바로 xargs의 일입니다.
가장 흔한 함정: 공백과 개행
xargs는 기본적으로 공백과 개행으로 입력을 쪼갭니다. 그래서 "내 문서.txt"처럼 공백이 든 파일 이름을 만나면 두 개의 인자로 잘못 갈라버립니다. 이건 단순한 불편이 아니라 실제로 파일을 잘못 지우는 사고로 이어집니다.
해결책은 NUL 문자(\0) 구분입니다. find의 -print0과 xargs의 -0을 짝지으면, 공백이 든 이름도 안전하게 하나로 취급됩니다.
# 위험: 공백/개행 든 이름에서 깨짐
find . -name '*.tmp' | xargs rm
# 안전: NUL 구분
find . -name '*.tmp' -print0 | xargs -0 rm
# grep도 -Z(=-print0 계열)로 짝을 맞출 수 있다
rg -l0 "deprecated" | xargs -0 sed -i 's/old/new/g'
나는 이 규칙을 "xargs를 쓰면 반사적으로 -0부터 떠올린다"로 몸에 새겼습니다. 한 번 공백 파일 때문에 데인 뒤로요.
인자를 명령의 중간에 넣기 — -I
기본 xargs는 받은 인자를 명령 맨 뒤에 붙입니다. 그런데 cp 파일 목적지처럼 인자를 중간에 끼워야 할 때가 있죠. 이때 -I로 치환 자리표시자를 지정합니다.
# 각 파일을 backup/ 아래로 복사
find . -name '*.conf' | xargs -I{} cp {} backup/
# 자리표시자는 여러 번 등장해도 된다
cat urls.txt | xargs -I{} curl -o {}.html https://example.com/{}
단, -I를 쓰면 입력 한 줄당 명령을 한 번씩 실행합니다. 즉 1000줄이면 프로세스를 1000번 띄웁니다. 기본 모드가 "여러 인자를 한 번에 몰아 실행"해 효율적인 것과 대조되니, 대량 처리에선 이 비용을 기억해 두세요.
진짜 무기: -P로 병렬 실행
xargs의 숨은 강점은 -P입니다. 인자 묶음을 동시에 여러 프로세스로 처리합니다. 이미지 1000장을 리사이즈하거나, URL 수백 개를 받아오는 작업이 코어 수만큼 빨라집니다.
# CPU 8코어를 다 써서 병렬로 이미지 변환
ls *.png | xargs -P 8 -I{} convert {} -resize 50% small/{}
# -P 0 은 "가능한 만큼 최대 병렬"
find . -name '*.mp4' -print0 | xargs -0 -P 0 -I{} ffmpeg -i {} ...
| 옵션 | 의미 |
|---|---|
-0 | NUL로 입력 구분(공백 안전) |
-n N | 한 번에 인자 N개씩 |
-P N | 동시 프로세스 N개 |
-I{} | 인자를 {} 자리에 치환 |
-r | 입력 없으면 실행 안 함 |
병렬로 돌릴 때 각 프로세스의 출력이 서로 뒤섞여 한 줄이 깨질 수 있습니다. 로그가 중요하면 각자 파일로 쓰게 하거나, GNU parallel처럼 출력 순서를 보장해 주는 도구를 쓰는 편이 낫습니다. xargs -P는 "빠르지만 출력은 얌전하지 않다"를 기억하세요.
빈 입력 사고 막기
GNU xargs는 입력이 비어 있어도 명령을 한 번 실행하려 합니다. find가 아무것도 못 찾았는데 xargs rm이 실행되면 낭패죠(BSD에선 다르게 동작). -r(--no-run-if-empty)을 붙이면 입력이 없을 때 아예 실행하지 않습니다. 자동화 스크립트라면 습관적으로 넣어두는 걸 권합니다.
자주 묻는 질문
xargs랑 -exec는 뭐가 다른가요?
find -exec cmd {} +도 인자를 모아 한 번에 실행해 xargs와 비슷합니다. find 안에서 끝난다는 게 장점이죠. 반면 xargs는 -P로 병렬 실행이 되고, find 외의 어떤 입력에도 쓸 수 있어 더 범용적입니다. 단순 파일 처리면 -exec ... +, 병렬이나 다른 파이프가 끼면 xargs를 씁니다.
"argument list too long" 오류가 났어요.
rm *처럼 셸이 인자를 다 펼치면 커널의 인자 길이 한계를 넘어 이 오류가 납니다. xargs가 바로 이 문제의 해법입니다. xargs는 인자를 알아서 한계 이하 크기로 여러 묶음으로 나눠 명령을 여러 번 실행하거든요. find . -name '*' -print0 | xargs -0 rm 식으로요.
-P로 몇 개를 병렬로 돌려야 적당한가요?
CPU 위주 작업이면 코어 수 근처(nproc 값), 네트워크·디스크 대기가 많은 작업이면 그보다 더 높게 잡아도 이득이 있습니다. -P 0은 제한 없이 최대로 띄우는데, 서버에 과부하를 줄 수 있으니 공유 환경에선 명시적인 숫자를 권합니다.
인자에 파이프나 리다이렉션을 쓰고 싶어요.
xargs가 실행하는 건 셸이 아니라 명령 하나라, |나 >는 동작하지 않습니다. 그럴 땐 xargs -I{} sh -c 'echo {} | ...'처럼 sh -c로 감싸야 합니다. 다만 이때 입력값이 셸에 그대로 들어가니, 신뢰할 수 없는 입력이면 인젝션에 주의해야 합니다.

댓글 0