Published on

리눅스 쉘 Argument list too long 해결 총정리

Authors

서버 운영이나 배포 스크립트를 짜다 보면 어느 순간 갑자기 아래 오류를 만나곤 합니다.

bash: /bin/rm: Argument list too long

대부분은 rm *.log, cp build/* /dest, chown -R user:group some/dir/*처럼 와일드카드 확장(globbing) 으로 엄청난 개수의 파일이 한 번에 인자로 펼쳐지면서 발생합니다. 이 글에서는 오류의 본질(커널/쉘의 인자 길이 제한)부터, 실무에서 재현·진단하고 안전하게 우회하는 패턴을 정리합니다.

> 대규모 시스템에서 이런 류의 오류는 “요청 헤더가 너무 커서 실패(HTTP 431)” 같은 현상과도 결이 비슷합니다. 제한값을 넘으면 갑자기 터지기 때문에, 원인 파악 → 제한 확인 → 분할 처리가 핵심입니다. 관련해서는 EKS에서 HTTP 431 해결 - 헤더·쿠키 과다 진단도 같은 접근법을 참고할 수 있습니다.

왜 발생하나: glob 확장 + ARG_MAX 제한

리눅스에서 프로그램을 실행할 때는 내부적으로 execve() 시스템 콜이 호출되며, 이때 전달되는 argv(인자 목록) + envp(환경 변수) 의 총 크기가 커널 제한을 넘으면 E2BIG 에러가 발생합니다. 쉘은 이를 사용자가 보기 쉬운 메시지로 바꿔 Argument list too long을 출력합니다.

핵심 포인트는 두 가지입니다.

  1. rm *.log에서 *.logrm이 처리하는 게 아니라 쉘이 먼저 파일 목록으로 확장합니다.
  2. 확장된 파일 경로가 수만~수십만 개가 되면, argv 전체 바이트 수가 제한을 넘어 터집니다.

제한값 확인: getconf ARG_MAX

getconf ARG_MAX

보통 수백 KB~수 MB 수준이지만, 환경 변수 크기까지 포함되므로 CI/CD 환경이나 컨테이너에서 환경 변수가 크면 더 빨리 한계에 도달할 수 있습니다.

추가로 현재 프로세스의 환경 변수 총량이 큰지 대략 확인하려면:

# 환경 변수 전체를 1줄로 합쳐 바이트 수를 대략 계산
python3 - <<'PY'
import os
s='\0'.join([f"{k}={v}" for k,v in os.environ.items()])
print(len(s))
PY

가장 흔한 재현 케이스

rm/cp/mv/chown에서 터지는 경우

rm /var/log/app/*.log
cp build/* /mnt/artifacts/
chown app:app /data/uploads/*

파일이 많을수록(특히 짧은 파일명이 아닌 긴 경로) argv가 급격히 커집니다.

find 결과를 커맨드 치환으로 넣다가 터지는 경우

# 위험: $(...)가 결과를 한 번에 펼쳐서 인자로 만듦
rm $(find . -type f -name '*.tmp')

이 패턴은 파일 개수에 비례해 100% 터지는 시한폭탄입니다.

해결 전략 1: find -exec (가장 안전한 기본기)

find -exec는 파일을 분할해서 커맨드를 실행할 수 있어, 인자 길이 제한을 우회합니다.

-exec ... ; (1개씩 실행)

find /var/log/app -type f -name '*.log' -exec rm -f {} \;
  • 장점: 가장 안전(파일명에 공백/개행이 있어도 안전)
  • 단점: 파일이 많으면 느릴 수 있음(프로세스를 너무 많이 띄움)

-exec ... + (여러 개씩 묶어서 실행)

find /var/log/app -type f -name '*.log' -exec rm -f {} +
  • 장점: 내부적으로 가능한 만큼 묶어서 실행 → 빠름
  • 안전성: xargs보다도 다루기 쉬운 편

해결 전략 2: xargs (성능 좋지만 옵션을 정확히)

xargs는 표준 입력을 받아 인자를 적당히 쪼개 실행합니다. 다만 파일명에 공백/개행/특수문자가 있을 수 있으므로, NUL 구분(-print0 + -0) 조합을 기본으로 권장합니다.

NUL-safe 조합

find /var/log/app -type f -name '*.log' -print0 | xargs -0 rm -f

병렬 처리(주의해서 사용)

find /data/uploads -type f -name '*.tmp' -print0 | xargs -0 -P 4 rm -f
  • -P 4: 4개 병렬 실행
  • 스토리지 I/O가 약한 환경에서는 오히려 느려지거나 부하가 커질 수 있습니다.

실행 전에 확인(-t / echo)

find . -type f -name '*.bak' -print0 | xargs -0 -t rm -f

해결 전략 3: rsync/tar로 “목록”을 넘기지 말고 “스트림”으로 처리

대량 파일 복사/이동에서 cp dir/* dest/는 터지기 쉽습니다. 이럴 땐 파일 목록을 argv로 만들지 않는 도구를 쓰는 게 정석입니다.

rsync (대량 파일 복사/동기화)

rsync -a --delete ./build/ /mnt/artifacts/
  • ./build/ 처럼 디렉터리 단위로 넘기면 인자 폭발이 없습니다.

tar 파이프(대량 이동/백업)

# src를 tar 스트림으로 만들어 dest에 풀기
cd /src && tar cf - . | (cd /dest && tar xpf -)
  • 파일 개수와 무관하게 안정적으로 동작
  • 권한/타임스탬프 유지 옵션은 환경에 맞게 조정

해결 전략 4: 셸 글롭을 “덜 위험하게” 쓰는 법

글롭 자체가 나쁜 건 아니지만, 파일이 많아질 수 있는 디렉터리에서는 피하는 게 좋습니다.

글롭 대신 디렉터리 단위로 처리

# 위험: build/*
# 권장: build/ 디렉터리 자체를 인자로
cp -a build/. /mnt/artifacts/
  • build/. 패턴은 “build 디렉터리의 내용”을 의미하면서도 인자 폭발을 피합니다.

Bash의 globstar(재귀 글롭)도 주의

shopt -s globstar
rm -f **/*.tmp

재귀 글롭은 특히 더 빨리 한계에 도달합니다. 재귀는 find로 전환하는 편이 안전합니다.

진단 팁: 무엇이 인자를 키우는지 빠르게 확인

파일 개수/최대 경로 길이 확인

# 개수
find . -type f | wc -l

# 가장 긴 경로 10개
find . -type f -printf '%p\n' | awk '{ print length, $0 }' | sort -nr | head

경로가 길수록 argv 바이트가 급격히 증가합니다.

커맨드 치환/배열 확장 점검

다음 패턴이 있다면 위험 신호입니다.

rm $(cat filelist.txt)
cmd "${arr[@]}"
  • $(...) 결과가 커지면 즉시 폭발
  • 배열도 결국 argv로 전달되므로 동일한 한계

실전 레시피 모음

1) 특정 확장자 대량 삭제

find /var/tmp -type f -name '*.cache' -exec rm -f {} +

2) 대량 chmod/chown

find /data -type f -name '*.sh' -exec chmod 755 {} +
find /data -exec chown app:app {} +

3) 특정 패턴만 제외하고 삭제

find /var/log/app -type f -name '*.log' ! -name 'keep-*.log' -exec rm -f {} +

4) 목록 파일을 안전하게 처리(NUL 구분 목록)

# 목록 생성
find . -type f -name '*.tmp' -print0 > /tmp/tmpfiles.list

# 목록 사용
cat /tmp/tmpfiles.list | xargs -0 rm -f

체크리스트: 재발 방지

  1. *.ext가 터졌다면, 글롭을 없애고 find -exec ... +로 전환한다.
  2. rm $(find ...) 같은 패턴을 보면 즉시 리팩터링한다.
  3. 복사/동기화는 cp dir/* 대신 rsync -a dir/ dest/ 또는 cp -a dir/. dest/를 우선 고려한다.
  4. CI/CD에서 환경 변수가 과도하게 커지면 한계가 빨리 온다(비밀값을 수백 개 주입하는 패턴 등). 제한값을 늘리는 게 아니라, 전달 방식을 바꾸는 쪽이 보통 정답이다.

마무리

Argument list too long은 “파일이 너무 많아서”라기보다, 정확히는 쉘이 확장한 인자/환경 변수의 총 바이트 수가 커널 제한(ARG_MAX)을 넘어서 발생합니다. 해결의 핵심은 단순합니다.

  • 인자를 한 번에 만들지 말고(글롭/커맨드 치환 지양)
  • find -exec ... + 또는 find ... -print0 | xargs -0처럼 분할 실행으로 바꾸고
  • 복사/이동은 rsync/tar처럼 스트리밍/디렉터리 단위 도구를 사용한다.

운영 환경에서 “갑자기” 터지는 제한 기반 오류를 다루는 관점은 다른 문제에도 그대로 적용됩니다. 예를 들어 요청이 커져서 실패하는 케이스는 EKS에서 HTTP 431 해결 - 헤더·쿠키 과다 진단처럼 제한과 페이로드를 먼저 계측하는 접근이 유효합니다.