빌드노트
공백 있는 파일 이름이 xargs에서 두 인자로 나뉜다
find의 줄 단위 출력을 기본 xargs로 읽으면 공백·개행에서 이름이 나뉘고 따옴표도 사라진다. GNU 4.9.0·4.10.0에서 인자 바이트를 비교하고, -print0/-0와 빈 입력의 -r 처리를 확인했다.
find로 찾은 파일은 네 개인데, 뒤에 연결한 명령에는 여섯 개의 인자가 들어갈 수 있다. find ... -print | xargs ...에서 파일 이름의 공백과 개행이 구분자로 해석되기 때문이다. 이름에 들어 있던 따옴표도 사라질 수 있다. 파일 이름 하나를 인자 하나로 전달하려면 출력의 `-print0`와 입력의 `-0`를 함께 맞춘다. GNU 환경에서 검색 결과가 없을 때 실행을 건너뛰려면 -r도 붙인다.
배치를 고칠 때는 파일을 변경하는 명령부터 실행하지 않는다. 먼저 그 자리에 인자 수와 각 인자의 바이트를 출력하는 명령을 연결한다. 아래 비교에서도 파일 내용은 처리하지 않았다.
파일 네 개를 읽었는데 인자는 여섯 개였다
운영 배치의 장애 기록이 아니라, Linux 컨테이너에서 합성 파일을 만들어 확인한 결과다. Debian 12의 GNU findutils 4.9.0과 Debian 13의 4.10.0, Python 3.12.15에서 같은 비교를 실행했다.
직접 확인하려면 GNU find·xargs와 Python 3가 있는 Linux에서 자신이 만든 빈 실습 폴더로 이동한다. 다음 입력은 그 안에 files 폴더와 빈 파일 네 개를 만든다. 이미 files가 있으면 폴더 생성 단계에서 중단된다. line\nbreak.txt의 \n은 Python이 실제 개행 한 바이트로 바꾼다.
python3 - <<'PY'
import os
os.mkdir("files")
for name in ["report 2026.txt", 'two"quotes".txt', "line\nbreak.txt", "-draft.txt"]:
with open(os.path.join("files", name), "xb"):
pass
PY
같은 입력을 두 방식으로 전달한다. Python의 sys.argv[1:]는 파일 이름으로 전달된 인자들이다. os.fsencode로 각 인자를 바이트로 되돌린 뒤, 이스케이프 표기와 16진수로 출력한다. 출력 앞의 b는 Python의 바이트 표기이며 이름의 일부가 아니다.
find ./files -type f -print | xargs python3 -c 'import os, sys
a = [os.fsencode(s) for s in sys.argv[1:]]
print("argc=" + str(len(a)))
for s in a:
print(repr(s), s.hex())
'
find ./files -type f -print0 | xargs -0 -r python3 -c 'import os, sys
a = [os.fsencode(s) for s in sys.argv[1:]]
print("argc=" + str(len(a)))
for s in a:
print(repr(s), s.hex())
'
기본 파이프의 실제 출력은 다음과 같았다. 나열 순서는 파일시스템에 따라 달라질 수 있다.
argc=6
b'./files/-draft.txt' 2e2f66696c65732f2d64726166742e747874
b'./files/line' 2e2f66696c65732f6c696e65
b'break.txt' 627265616b2e747874
b'./files/twoquotes.txt' 2e2f66696c65732f74776f71756f7465732e747874
b'./files/report' 2e2f66696c65732f7265706f7274
b'2026.txt' 323032362e747874
report 2026.txt와 개행을 포함한 이름이 각각 두 인자로 나뉘었다. two"quotes".txt에서는 따옴표 두 개가 없어졌다. 기본 xargs는 줄 전체를 파일 이름으로 취급하지 않는다. 공백·개행으로 인자를 나누며 따옴표와 역슬래시를 입력 해석에 사용한다. GNU xargs 매뉴얼의 DESCRIPTION과 -0 설명 (새 탭에서 열림)이 이 규칙을 설명한다.
별도로 작은따옴표 하나가 들어간 solo'quote.txt도 전달했다. 기본 모드에서는 unmatched single quote 오류와 종료 코드 1이 나왔고, 인자 출력 명령은 실행되지 않았다. NUL 구분으로 바꾸자 작은따옴표까지 포함한 인자 하나가 전달됐다. 따옴표는 파일 이름에 쓸 수 있지만, 기본 xargs 입력에서는 문법으로 읽힌다.
NUL은 이름 사이에만 들어간다
-print0 | xargs -0 -r의 실제 출력에서는 원래 네 이름이 그대로 남았다.
argc=4
b'./files/-draft.txt' 2e2f66696c65732f2d64726166742e747874
b'./files/line\nbreak.txt' 2e2f66696c65732f6c696e650a627265616b2e747874
b'./files/two"quotes".txt' 2e2f66696c65732f74776f2271756f746573222e747874
b'./files/report 2026.txt' 2e2f66696c65732f7265706f727420323032362e747874
개행인 0a, 큰따옴표인 22, 공백인 20이 각각 인자 안에 보존됐다. 두 버전 모두 인자 수뿐 아니라 네 경로의 전체 바이트가 원래 이름과 일치했다. 기본 파이프도 종료 코드는 0이었으므로, 명령 성공 여부만 보면 이 손상을 놓친다.
find -print0는 경로 뒤에 NUL 바이트(00)를 붙이고, xargs -0는 그 바이트를 인자의 경계로 읽는다. 구분용 NUL 자체는 인자 내용에 포함되지 않는다. GNU의 안전한 파일 이름 전달 설명 (새 탭에서 열림)은 이 두 옵션을 짝지어 안내한다. Debian이 제공하는 GNU find의 -print0 설명 (새 탭에서 열림)에서도 이름을 그대로 출력하고 NUL로 끝내는 규칙을 확인할 수 있다.
왜 개행 대신 NUL을 쓸까. Linux 경로는 NUL로 끝나는 바이트열이므로 파일 이름 자체에 NUL을 넣을 수 없다. /는 경로 구성요소를 나누는 문자다. 반면 공백·따옴표·개행은 이번 Linux 실험에서 실제 파일 이름으로 만들 수 있었다. Linux pathname 매뉴얼 (새 탭에서 열림)이 이 경계를 설명한다. 이름에 들어갈 수 없는 바이트를 구분자로 쓰므로 개행이 포함된 이름도 한 인자로 보존된다.
양쪽 중 한 곳만 바꾼 비교도 실패했다.
-print | xargs -0: 이 입력에서는 개행까지 포함한 출력 전체가 인자 하나로 합쳐졌다. 출력에 NUL 경계가 없기 때문이다.-print0 | xargs: 이 입력에서는 NUL을 만났다는 경고와 함께 인자 세 개가 전달됐다. 원래 네 경로를 보존하지 못했다.
개행 목록을 만든 뒤 개행을 NUL로 치환하는 방식도 이미 잃어버린 경계를 복원할 수 없다. 이름 안의 개행과 이름 사이의 개행을 그 시점에는 구별할 수 없기 때문이다. 중간에 목록을 가공하는 단계가 있다면 그 단계도 NUL 구분을 유지하는지 확인한다.
결과가 없을 때와 하이픈으로 시작할 때
빈 디렉터리에서도 같은 출력 도구로 확인했다. GNU 4.9.0과 4.10.0 사이에서 이 동작의 차이는 없었다.
- 기본 모드와
-0만 쓴 모드: 출력 도구가 한 번 실행됐고argc=0이었다. -r를 붙인 모드와-0 -r모드: 출력 도구가 실행되지 않았다.
따라서 -0는 빈 결과 처리를 대신하지 않는다. 빈 입력에서 명령을 실행할지와, 들어온 이름을 어디에서 나눌지는 서로 다른 설정이다. GNU 4.9.0의 -r 설명 (새 탭에서 열림)과 4.10.0의 설명 (새 탭에서 열림) 모두 이 기본값과 GNU 확장 옵션임을 명시한다. 자기 환경에서는 xargs --version으로 구현과 버전을 확인한다. 이번에는 BSD와 BusyBox의 동작을 실행하지 않았다.
-draft.txt도 인자 하나로 보존됐지만, NUL 구분은 대상 명령의 옵션 해석까지 바꾸지는 않는다. 위 예제에서는 find ./files가 ./files/-draft.txt를 출력하므로 인자의 첫 문자가 하이픈이 아니다. 별도 비교에서 파일 이름 부분만 출력하자 -0를 거쳐도 인자는 그대로 -draft.txt였다. 실제 처리 명령이 --로 옵션 끝을 표시하는 문법을 지원한다면, 그 명령의 옵션 뒤와 파일 인자 앞에 둔다. xargs 자신의 옵션을 끝내는 --와는 위치가 다르다. 이름만 꺼내는 가공을 추가했다면 이 조건도 다시 봐야 한다.
자신의 배치에서는 기존 검색 조건을 유지한 채 -print0와 -0 -r를 맞추고, 위 출력 도구로 인자 수와 바이트부터 확인한다. 이 작은 입력과 달리 파일이 많으면 xargs가 여러 번 호출할 수 있으므로 각 호출의 인자를 모두 본다. 예상과 다르면 실제 파일을 처리하기 전에 중간 단계에서 경계가 바뀌었는지 찾는다. 여기서 확인한 범위는 합성 파일의 인자 전달이며, 파일을 나열한 뒤 다른 프로세스가 교체하는 경쟁 상황이나 삭제 작업의 안전성까지 검증한 것은 아니다.