macOS에서 xargs -d가 invalid option 오류를 낼 때: 빈 입력과 종료 코드가 어긋나는 경우까지 직접 시험한 결과When xargs -d fails with invalid option on macOS: what I tested, including the cases where empty input and exit codes silently differ
리눅스식 xargs -d 스크립트를 macOS에서 돌려 오류를 만났거나, 오류 없이 다른 결과가 나와 당황한 독자가 원인을 가려내고 맥과 리눅스 양쪽에서 같은 인자를 받는 형태로 바꾸게 돕는다.
이 맥(26.6.2)의 xargs 에는 -d 가 없어 사용법 안내와 함께 종료 코드 1이 되지만, 줄 끝을 NUL 로 바꿔 -0 에 넘기면 두 xargs 가 같은 인자를 받는다. 다만 빈 입력에서의 실행 여부, 실패 종료 코드, -0 에서의 빈 인자는 오류 없이 다르고 -I 는 기본 설정에서 맥만 255바이트부터 실패하므로, 빈 줄을 지우고 -r 을 붙인 함수로 감싸되 -I 는 따로 조심한다.
스크립트에서 xargs -d 를 쓰는 곳을 찾아 입력이 파일 이름 목록인지 확인하고, find -print0 이나 아래 함수로 바꾼 뒤 맥과 리눅스에서 같은 인자 목록이 나오는지 비교한다.
목차
한 줄 요약: 이 맥의 xargs 에는 -d 가 없어 줄 단위 입력은 tr 로 NUL 을 만들어 -0 에 넘기면 되는데, 빈 입력·종료 코드·빈 줄은 오류 없이 다르게 동작하므로 빈 줄을 지우고 -r 을 붙인 함수로 감싼다.
리눅스에서 쓰던 printf 'a\nb c\n' | xargs -d '\n' printf '[%s]' 를 맥 터미널에 붙였더니 아래처럼 실패한다. 이 글은 무엇으로 바꾸면 되는지, 그리고 바꾼 뒤에도 오류 없이 결과가 달라지는 자리를 이 맥에서 두 xargs 로 나란히 돌려 보인다.
xargs: invalid option -- d
usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements] [-S replsize]]
[-J replstr] [-L number] [-n number [-x]] [-P maxprocs]
[-s size] [utility [argument ...]]
같은 사정은 공개 기록에도 있다. 2017년 CMake 의 clang-format 스크립트 수정 요청은 “xargs on Mac does not support -d, but does support -0” 이라고 적고 -d 를 대체했고, 2020년 OpenJDK 의 JDK-8238204 는 macOS 의 xargs 가 --no-run-if-empty 를 받지 못한다고 보고했다. 확인한 환경은 macOS 26.6.2 arm64 의 /usr/bin/xargs(BSD 계열), Homebrew 의 GNU findutils 4.11.0 에 든 gxargs, bash 3.2.57, 시간대 Asia/Seoul(KST)이다. GNU 쪽은 리눅스가 아니라 이 맥에 설치한 gxargs 로 시험했다.
용어 몇 개. xargs 는 표준 입력으로 받은 글자를 쪼개 다른 명령의 인자로 붙여 실행하는 명령이다. 종료 코드는 명령이 끝나며 남기는 숫자로 0이 정상이다. -0 은 입력을 줄바꿈이 아니라 NUL(글자 값 0인 바이트)로 끊어 읽는 옵션이고, find -print0 은 파일 이름을 NUL 로 끊어 내보낸다. 파이프(|)는 앞 명령의 출력을 뒤 명령의 입력으로 넘기는 연결이다.
이 맥의 xargs 에는 -d 가 없다
/usr/bin/xargs -d '\n' 은 위 오류와 함께 종료 코드 1을 낸다. 이 맥의 man xargs 옵션 목록(-0 -E -I -J -L -n -o -P -p -r -R -S -s -t -x)에는 -d 가 없다. 같은 입력을 gxargs -d '\n' 에 주면 [a][b c] 가 나왔다. 리눅스 쪽 man7.org 의 xargs(1) 는 -d delim 을 “Input items are terminated by the specified character” 라 적고, 이때 따옴표와 역슬래시는 특별하지 않다고 설명한다. 즉 -d '\n' 은 줄 하나를 인자 하나로 읽을 때 흔히 쓰는 방법이고, 이 맥의 xargs 는 그 기능을 구현하지 않았다.
줄 단위 입력은 tr 과 -0 으로 바꾼다
입력이 find 에서 오면 find -print0 | xargs -0 이 두 xargs 에서 모두 돌았다. 입력이 일반 줄 목록이면 줄바꿈을 NUL 로 바꿔 -0 에 넘기면 된다. 아래 시험 폴더에는 이름이 a b.log, it's.log, q"x".log, plain.log 인 파일이 있고, 인자 목록은 두 xargs 에서 같았다.
find d -name '*.log' -print0 | xargs -0 printf '[%s]'
ls d | sed 's|^|d/|' | tr '\n' '\0' | xargs -0 printf '[%s]'
기본 xargs(옵션 없음)는 공백으로 쪼개므로 d/a b.log 가 [d/a][b.log] 두 인자가 되었고 두 xargs 가 같았다. 이름에 홑따옴표가 든 d/it's.log 는 둘 다 오류를 내고 종료 코드 1이었다. 문구만 다르다. 맥은 xargs: unterminated quote, GNU 는 unmatched single quote; by default quotes are special to xargs unless you use the -0 option 이다. 쌍따옴표 하나(x"y)도 같았다. 역슬래시로 공백을 막은 a\ b 와 쌍따옴표로 묶은 "c d" e 는 두 xargs 가 같게 [a b], [c d][e] 로 읽었다.
오류 없이 달라지는 자리
-d 를 -0 으로 바꿨다고 끝이 아니다. 같은 입력을 두 xargs 에 주고 결과를 비교하면 아래가 다르다(-r 행과 없는 명령 행은 같은 것을 확인하려고 넣은 대조 행이다). 입력은 printf 로 만들었고 두 번 실행해 같은 출력을 얻었다.
| 상황 | 이 맥의 xargs | gxargs (GNU 4.11.0) |
|---|---|---|
입력이 비었을 때 xargs echo RAN |
아무것도 실행하지 않음, 종료 코드 0 | RAN 이 한 번 찍힘 |
같은 경우 xargs -r echo RAN |
아무것도 실행하지 않음 | 아무것도 실행하지 않음 |
| 명령이 종료 코드 1·2 로 끝났을 때 xargs 의 종료 코드 | 1 | 123 |
| 명령이 종료 코드 255 로 끝났을 때 | 1 (sh: exited with status 255; aborting) |
124 (접두사만 다른 같은 문구) |
| 없는 명령 | 127 | 127 |
tr 로 만든 NUL 입력에서 빈 줄(a, 빈 줄, b) |
-0 이 빈 인자를 버려 [a][b] |
빈 인자를 살려 [a][][b] |
-I{} 에 길이 300짜리 줄 |
xargs: command line cannot be assembled, too long, 종료 코드 1 |
정상, 길이 300 |
표의 첫 두 행이 가장 조용하다. 이 맥의 man 을 요약하면 FreeBSD 판은 빈 입력이면 명령을 실행하지 않고 -r 은 아무 일도 하지 않는다고 적고, man7 은 GNU 쪽이 “Normally, the command is run once even if there is no input” 이라고 적는다. find 가 아무것도 못 찾았을 때 xargs ls keep 같은 줄은 GNU 에서 ls keep 이 한 번 돌아 f1 f2 를 찍었고 맥에서는 아무것도 하지 않았다. -r 은 맥에서도 오류가 아니므로 두 쪽에 모두 붙여 동작을 맞출 수 있다.
종료 코드도 조심할 점이다. 맥 man 은 xargs 가 명령을 못 찾으면 127, 실행하지 못하면 126, 그 밖의 오류는 1이라고 적을 뿐 명령 자체가 1이나 2로 끝난 경우를 따로 구분해 적지 않으며, 이 맥에서 시험한 결과도 1이었다. man7 은 어떤 호출이 0이나 255가 아닌 값으로 끝나면 123, 255면 124라 적는다. 스크립트가 [ $? -eq 123 ] 으로 실패를 가렸다면 맥에서는 모두 빠져나간다. 0이 아닌지만 보는 쪽이 안전하다.
빈 줄 행에서 어긋나는 쪽은 맥의 -0 하나다. gxargs 는 -d '\n' 으로 읽든 tr 로 만든 NUL 을 -0 으로 읽든 [a][][b] 로 빈 인자를 살렸고, 맥만 -0 에서 빈 인자를 버린다. 마지막 줄 끝에 줄바꿈이 없어도 두 xargs 가 [a][b] 로 같았다. -I 의 길이는 맥 man 이 -S replsize 의 기본값을 255바이트로 적는다. 이 시험(xargs -I{} sh -c 'echo ${#1}' _ {})에서는 길이 254까지 정상이고 255부터 위 오류였고, -S 1024 를 주면 길이 300이 정상으로 돌았다. -I 없이 -n1 로 넘기면 맥에서도 길이 300이 정상이었다. 이 값은 명령 틀 하나로 잰 것이어서, 치환할 자리가 더 많은 틀에서 한계가 달라지는지는 시험하지 않았다.
실패가 파이프 끝에서 묻힌다
-d 오류를 2>/dev/null 로 버린 채 뒤에 sort 를 붙이면 스크립트는 실패를 모른다. 맥에서 printf 'a\n' | xargs -d '\n' echo 2>/dev/null | sort 를 돌린 직후 bash 의 PIPESTATUS 는 0 1 0(printf, xargs, sort)이었고, 파이프 전체의 종료 코드는 마지막 sort 의 0 이다. 리눅스에서 잘 돌던 줄이 맥에서 오류도 없이 빈 결과로 넘어가는 증상이 이렇게 생긴다. PIPESTATUS 는 bash 전용이라 zsh 에서는 비어 있으니 zsh 에서는 pipestatus 로 봐야 하지만 이 글에서는 zsh 를 시험하지 않았다.
두 xargs 에서 같은 줄을 내는 함수
빈 줄을 지우고, 줄바꿈을 NUL 로 바꾸고, -r 을 붙이면 위 표의 빈 입력·빈 줄 행이 맞춰진다.
xargs_lines() { # xargs_lines 명령 [인자...] : 표준 입력의 각 줄(빈 줄 제외)을 인자 끝에 붙여 실행
sed '/^$/d' | tr '\n' '\0' | xargs -0 -r "$@"
}
PATH 맨 앞에 /usr/bin/xargs 를 가리키는 링크 폴더를 둔 경우와 gxargs 를 xargs 이름으로 가리키는 링크 폴더를 둔 경우에 각각 이 함수를 돌렸다. 이름 네 개를 ls 로 넘긴 경우는 두 쪽 모두 [d/a b.log][d/it's.log][d/plain.log][d/q"x".log], x·빈 줄·y 는 [x][y], 빈 입력은 아무것도 실행하지 않고 종료 코드 0, 끝 줄바꿈이 없는 p q·r 은 [p q][r] 로 같았다. -I{} 도 같은 틀(sed '/^$/d' | tr '\n' '\0' | xargs -0 -r -I{} …)로 <k l> 과 <m> 이 같았다. 다만 함수의 종료 코드는 xargs 의 것이라 실패하는 명령을 주면 맥 1, GNU 123 으로 여전히 다르다. 그래서 값이 0인지 아닌지만 보라고 위에서 적었다. -I 의 255바이트 한계도 이 함수로 해결되지 않는다. -I{} 에 긴 줄을 주면 맥은 같은 오류를 내므로 -S 를 늘리거나 -I 없이 -n1 로 넘긴다. 또 이 함수도 줄 하나가 인자 하나여야 하므로 줄바꿈이 든 파일 이름은 못 다룬다. 그런 이름은 find -print0 | xargs -0 -r 로 넘긴다.
이 PATH 링크 폴더는 Homebrew findutils 가 안내하는 gnubin 디렉터리를 PATH 앞에 둔 맥을 흉내 낸 것이다. 이 글의 해석으로는 그런 맥에서 xargs 가 GNU 판이 되어 -d 가 그대로 돌겠지만 같은 스크립트를 gnubin 없는 맥에 가져가면 다시 실패한다. 실제 gnubin 은 쓰지 않았다.
시험하지 못한 것
리눅스에서 도는 GNU xargs 와 다른 findutils 버전, BusyBox xargs, 다른 macOS 버전, -P 병렬 실행의 출력 순서와 종료 코드, 명령줄 길이 한계(-s)에 걸리는 큰 입력, -L 과 -n 의 조합, 줄바꿈이 든 파일 이름, 여러 글자 구분자, zsh 에서의 파이프 종료 코드와 set -o pipefail 은 확인하지 않았다. 종료 코드 표는 sh -c 'exit N' 으로 만든 경우이고, 실제 명령의 종료 코드는 그 명령에 달려 있다.
세 줄 요약
- 이 맥의 xargs 에는 -d 가 없어
xargs: invalid option -- d와 종료 코드 1이 나오므로 find -print0 이나 tr ‘\n’ ‘\0’ 로 만든 입력을 -0 에 넘긴다. - 바꾼 뒤에도 빈 입력에서 GNU 만 명령을 한 번 실행하고, 실패 종료 코드는 맥 1 대 GNU 123·124이며, -0 에서 맥만 빈 인자를 버리고, -I 는 맥에서 기본 설정으로 255바이트부터 실패한다.
- 빈 줄을 지우고 -r 을 붙인 함수로 감싸 두 xargs 에서 같은 인자를 만들되, 종료 코드는 0 인지 아닌지만 본다.
To help a reader who ran a Linux-style xargs -d script on macOS and hit an error, or got a different result with no error, find the cause and rewrite the script so the Mac and Linux receive the same arguments.
The xargs on this Mac (26.6.2) has no -d, so it prints a usage message and exits with 1, but turning line ends into NUL and passing them to -0 gives both xargs the same arguments. Whether the command runs on empty input, the exit code on failure, and empty arguments under -0 differ without any error, and -I fails from 255 bytes on the Mac only under default settings, so wrap it in a function that deletes empty lines and adds -r, and treat -I separately.
Find where your scripts use xargs -d, check whether the input is a list of file names, replace it with find -print0 or the function below, and compare that the Mac and Linux produce the same argument list.
Contents
One-line summary: The xargs on this Mac has no -d, so for line-based input you make NUL with tr and pass it to -0, but empty input, exit codes and empty lines behave differently without any error, so wrap it in a function that deletes empty lines and adds -r.
I pasted printf 'a\nb c\n' | xargs -d '\n' printf '[%s]', which I had been using on Linux, into a Mac terminal, and it failed like this. This article shows what to replace it with, and then, running the two xargs side by side on this Mac, the places where results differ even after the replacement, without any error.
xargs: invalid option -- d
usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements] [-S replsize]]
[-J replstr] [-L number] [-n number [-x]] [-P maxprocs]
[-s size] [utility [argument ...]]
Public records show the same situation. In 2017, a CMake merge request that fixed its clang-format script wrote “xargs on Mac does not support -d, but does support -0” and replaced -d, and in 2020 OpenJDK’s JDK-8238204 reported that the xargs on macOS does not accept --no-run-if-empty. The environment I checked is macOS 26.6.2 arm64 with /usr/bin/xargs (BSD family), gxargs from Homebrew’s GNU findutils 4.11.0, bash 3.2.57, and the Asia/Seoul (KST) time zone. I tested the GNU side not on Linux but with the gxargs installed on this Mac.
A few terms. xargs is a command that splits the text it receives on standard input and appends the pieces as arguments to another command. An exit code is the number a command leaves when it finishes, and 0 means success. -0 is an option that reads the input as ending at NUL (a byte whose value is 0) instead of at a newline, and find -print0 emits file names ending in NUL. A pipe (|) is the connection that passes the output of the earlier command to the later command as its input.
The xargs on this Mac has no -d
/usr/bin/xargs -d '\n' prints the error above and exits with 1. The option list in this Mac’s man xargs (-0 -E -I -J -L -n -o -P -p -r -R -S -s -t -x) has no -d. Giving the same input to gxargs -d '\n' produced [a][b c]. The xargs(1) page on man7.org for Linux describes -d delim as “Input items are terminated by the specified character” and explains that quotes and backslashes are not special then. So -d '\n' is a method commonly used to read one line as one argument, and the xargs on this Mac does not implement that feature.
Convert line-based input with tr and -0
When the input comes from find, find -print0 | xargs -0 worked on both xargs. When the input is an ordinary list of lines, you convert the newlines to NUL and pass them to -0. The test folder below has files named a b.log, it's.log, q"x".log and plain.log, and the argument list was the same on both xargs.
find d -name '*.log' -print0 | xargs -0 printf '[%s]'
ls d | sed 's|^|d/|' | tr '\n' '\0' | xargs -0 printf '[%s]'
The plain xargs (no options) splits on spaces, so d/a b.log became the two arguments [d/a][b.log], and both xargs were the same. d/it's.log, whose name contains a single quote, produced an error on both with exit code 1. Only the wording differs. The Mac says xargs: unterminated quote, and GNU says unmatched single quote; by default quotes are special to xargs unless you use the -0 option. A single double quote (x"y) behaved the same way. a\ b, where a backslash protects the space, and "c d" e, grouped with double quotes, were read the same way by both xargs, as [a b] and [c d][e].
Where results differ without any error
Swapping -d for -0 is not the end of it. Giving the same input to the two xargs and comparing the results shows the differences below (the -r row and the missing-command row are control rows, included to confirm that the two behave the same). The input was made with printf, and two runs gave the same output.
| Situation | xargs on this Mac | gxargs (GNU 4.11.0) |
|---|---|---|
xargs echo RAN when the input is empty |
runs nothing, exit code 0 | RAN is printed once |
xargs -r echo RAN in the same case |
runs nothing | runs nothing |
| xargs exit code when the command ended with exit code 1 or 2 | 1 | 123 |
| when the command ended with exit code 255 | 1 (sh: exited with status 255; aborting) |
124 (the same message, only the prefix differs) |
| a command that does not exist | 127 | 127 |
an empty line (a, empty line, b) in NUL input made with tr |
-0 drops the empty argument, giving [a][b] |
keeps the empty argument, giving [a][][b] |
-I{} with a line 300 characters long |
xargs: command line cannot be assembled, too long, exit code 1 |
works, length 300 |
The first two rows of the table are the quietest. To summarize this Mac’s man page, the FreeBSD version does not run the command on empty input and -r does nothing, and man7 says that for GNU “Normally, the command is run once even if there is no input”. A line like xargs ls keep, when find found nothing, ran ls keep once on GNU and printed f1 f2, and on the Mac did nothing. -r is not an error on the Mac either, so you can add it on both sides to make the behavior match.
The exit code is also something to watch. This Mac’s man page says that xargs exits with 127 if it cannot find the command, 126 if it cannot run it, and 1 for any other error, but it does not separately describe the case where the command itself ended with 1 or 2, and the result I tested on this Mac was also 1. man7 says it is 123 when any invocation ends with a value other than 0 or 255, and 124 for 255. If a script told failures apart with [ $? -eq 123 ], on the Mac every failure slips through. It is safer to look only at whether the code is non-zero.
In the empty-line row, the one that diverges is the Mac’s -0 alone. gxargs kept the empty argument as [a][][b] whether it read with -d '\n' or read NUL made with tr using -0, and only the Mac drops the empty argument under -0. Even when the last line had no trailing newline, both xargs gave the same [a][b]. For the length of -I, this Mac’s man page gives the default of -S replsize as 255 bytes. In this test (xargs -I{} sh -c 'echo ${#1}' _ {}), lengths up to 254 worked and from 255 the error above appeared, and with -S 1024 length 300 worked. Passing the line with -n1 and no -I also worked at length 300 on the Mac. This value was measured with one command template, so I did not test whether the limit changes with a template that has more places to substitute.
Failure gets buried at the end of a pipe
If you throw the -d error away with 2>/dev/null and add sort after it, the script does not know about the failure. On the Mac, right after running printf 'a\n' | xargs -d '\n' echo 2>/dev/null | sort, bash’s PIPESTATUS was 0 1 0 (printf, xargs, sort), and the exit code of the whole pipe is the 0 of the last sort. This is how a line that worked on Linux moves on to an empty result on the Mac without any error. PIPESTATUS is bash-only, so in zsh it is empty and you have to look at pipestatus, but I did not test zsh in this article.
A function that gives the same lines on both xargs
Deleting empty lines, converting newlines to NUL and adding -r makes the empty-input and empty-line rows of the table above match.
xargs_lines() { # xargs_lines command [args...] : append each line of standard input (empty lines excluded) to the end of the arguments and run
sed '/^$/d' | tr '\n' '\0' | xargs -0 -r "$@"
}
I ran this function with a link folder at the front of PATH pointing xargs to /usr/bin/xargs, and with a link folder pointing xargs to gxargs. Passing four names through ls gave the same [d/a b.log][d/it's.log][d/plain.log][d/q"x".log] on both sides, x, an empty line and y gave [x][y], empty input ran nothing and exited with 0, and p q and r with no trailing newline gave [p q][r], all the same. -I{} with the same shape (sed '/^$/d' | tr '\n' '\0' | xargs -0 -r -I{} …) also gave the same <k l> and <m>. However, the exit code of the function is xargs’s, so if you give it a failing command it is still different, 1 on the Mac and 123 on GNU. That is why I wrote above that you should look only at whether the value is 0. The 255-byte limit of -I is also not solved by this function. If you give -I{} a long line, the Mac gives the same error, so raise -S or pass the line with -n1 and no -I. Also, since this function needs one line to be one argument, it cannot handle file names that contain newlines. Pass such names with find -print0 | xargs -0 -r.
This PATH link folder imitates a Mac where the gnubin directory advertised by Homebrew’s findutils is placed at the front of PATH. My interpretation in this article is that on such a Mac xargs becomes the GNU version and -d would work as is, but taking the same script to a Mac without gnubin makes it fail again. I did not use a real gnubin.
What I could not test
I did not check GNU xargs running on Linux and other findutils versions, BusyBox xargs, other macOS versions, the output order and exit codes of -P parallel runs, large input that hits the command-line length limit (-s), combinations of -L and -n, file names that contain newlines, multi-character delimiters, or the pipe exit codes and set -o pipefail in zsh. The exit code table covers cases made with sh -c 'exit N', and the exit code of a real command depends on that command.
Three-line summary
- The xargs on this Mac has no -d and gives
xargs: invalid option -- dwith exit code 1, so pass input made with find -print0 or tr ‘\n’ ‘\0’ to -0. - Even after the change, only GNU runs the command once on empty input, the failure exit code is 1 on the Mac against 123 or 124 on GNU, only the Mac drops empty arguments under -0, and -I fails on the Mac from 255 bytes under default settings.
- Wrap it in a function that deletes empty lines and adds -r so both xargs build the same arguments, and look only at whether the exit code is 0 or not.