macOS에서 cp --parents가 illegal option 오류를 낼 때: 폴더 구조째 복사하는 다른 방법 여섯 가지를 직접 시험한 결과When cp --parents fails with illegal option on macOS: six other ways to copy files with their folder structure, tested directly
리눅스식 cp --parents 를 맥에서 돌려 오류를 만났거나 오류 없이 파일이 안 복사된 독자가, 원인을 가려내고 자기 상황(링크·권한·한글 이름)에 맞는 대체 방법을 고르게 돕는다.
이 맥(26.6.2)의 cp 는 --parents 뿐 아니라 -t·-u·--backup·--archive 같은 GNU 옵션을 illegal option 과 종료 코드 64로 거부하고, 오류를 버린 find -exec cp --parents {} dest \; 는 아무것도 복사하지 않고 종료 코드 0으로 끝난다(끝을 + 로 바꾸면 1). 8개 파일 대조에서 폴더를 만들고 cp -p 를 하는 함수와 rsync -aRL, pax -L, cpio -L, ditto 는 GNU cp -p --parents 와 파일 단위로 같았고, rsync -aR 은 링크를 링크로 남겼고, tar 는 한글 이름을 분해형으로 바꿨다. 700 권한 폴더는 ditto·rsync 는 따라갔고 함수·tar·pax·cpio 는 755 로 만들었다.
스크립트에서 cp --parents 를 쓰는 곳을 찾아 아래 함수로 바꾸고, 없는 파일을 하나 섞어 종료 코드가 0이 아닌지 한 번 돌려 보라.
목차
한 줄 요약: 이 맥(26.6.2)의 cp 는 –parents 를 종료 코드 64로 거부하고 오류를 버린 find -exec … ; 는 종료 코드 0으로 끝나므로, 폴더를 만들고 cp -p 를 하는 함수나 rsync -aRL 을 쓴다.
리눅스에서 cp --parents src/a/b/c.txt dest 로 폴더 구조를 그대로 둔 채 파일을 복사하던 스크립트를 맥 터미널에 붙였더니 아래처럼 실패한다. 이 글은 그 오류가 왜 나는지, 오류를 버렸을 때 무슨 일이 생기는지, 맥에서 같은 결과를 내는 방법(방법 여섯 가지, 옵션 변형까지 표로는 일곱 줄)이 어디서 갈리는지를 이 맥에서 직접 돌려 보인다.
cp: illegal option -- -
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-aclpSsvXx] source_file target_file
확인한 환경은 macOS 26.6.2 arm64 의 /bin/cp, 비교 기준으로 Homebrew 의 GNU coreutils 9.11 에 든 gcp, 맥에 기본으로 든 rsync(openrsync, 프로토콜 29), bash 3.2.57 이고 날짜는 2026-10-27 이다. 시험 스크립트는 LC_ALL=C 로 두 번씩 돌렸고 두 번째 결과는 첫 번째와 같았다. 같은 증상과 오류 문구는 이미 공개돼 있다. Stack Overflow 의 질문 「cp –parents option on mac」(2012년 질문, 점수 74, 조회수 37,521 — 2026-10-27 조회)이 있고, rmlint 이슈 275(2018)는 같은 cp: illegal option -- - 를 보고하고, 댓글이 --remove-destination 이 BSD 에 없는 옵션이라서 났다고 설명한다.
용어 몇 개. cp --parents 는 GNU 설명서에 따르면 대상 폴더 뒤에 원본 파일의 이름을 그대로 붙여 복사하고 빠진 중간 폴더를 만든다. 예를 들어 cp --parents a/b/c existing_dir 는 existing_dir/a/b/c 를 만든다. GNU 는 많은 리눅스 배포판이 쓰는 명령 모음이고, 맥의 명령은 BSD 계열이라 옵션이 다르다. 종료 코드는 명령이 끝나며 남기는 숫자로 0이 정상이다. PIPESTATUS 는 bash 가 파이프의 명령별 종료 코드를 담는 배열이다. 심볼릭 링크는 다른 파일을 가리키는 바로가기 파일이다.
맥의 cp 가 거부하는 옵션과 받아 주는 옵션
--parents 는 illegal option -- - 와 종료 코드 64였다. 메시지가 어떤 옵션인지 말해 주지 않는다. 짧은 옵션 - 한 글자만 보이므로 긴 옵션이 통째로 거부됐다는 것을 알기 어렵다. 같은 방식으로 --target-directory, --backup, --update, --preserve=all, --no-clobber, --reflink=auto, --archive 도 64였고, 짧은 -t, -T, -u 도 illegal option 과 64였다.
| 옵션 | 맥 /bin/cp |
gcp 9.11 |
|---|---|---|
--parents |
64 | 0 |
-t 폴더, --target-directory |
64 | 0 |
-T, -u, --update, --backup, --no-clobber |
64 | 0 |
--preserve=all, --reflink=auto, --archive |
64 | 0 |
-a, -p, -n, -R, -r, -v |
0 | 0 |
-c(clonefile 복제), -X |
0 | 1 (invalid option) |
마지막 줄은 반대 방향이다. 맥에만 있는 -c·-X 는 GNU 쪽이 거부한다. GNU 설명서의 --parents 가 하는 일을 맥의 옵션만으로는 한 번에 못 하니, 폴더를 만드는 일을 따로 해야 한다.
오류를 버리면 아무것도 복사하지 않고 성공이라고 한다
스크립트에서는 이 줄이 보통 find 에 붙어 있다. 오류 화면이 지저분해 2>/dev/null 을 붙인 채 이 맥에서 돌렸다. 대상은 src/a/b/c.txt, src/a/d.txt 두 파일이었다.
find src -name '*.txt' -exec cp --parents {} dest \; 2>/dev/null
echo $? # 0
find dest -mindepth 1 | wc -l # 0
find 안에서 cp 의 종료 코드를 따로 찍어 보면 파일마다 64로 실패했는데 find 는 0으로 끝났고 dest 에는 항목이 하나도 없었다. 끝을 + 로 바꾸면(-exec cp --parents {} dest +) find 가 1을 돌려줬다. xargs -0 -I{} cp --parents {} dest 도 xargs 가 1로 끝나서 PIPESTATUS 가 0 1 이었다. 세 방식 모두 dest 는 비어 있었다. 그래서 \; 형태에서 복사 뒤에 ls dest 만 보는 검사는 폴더가 이미 있으니 통과했고, [ -e dest/src/a/d.txt ] 로 파일을 직접 본 검사만 “복사 안 됨”을 알려 줬다.
같은 find ... \; 를 gcp 로 바꾸면 dest 아래에 src/a/b/c.txt 와 src/a/d.txt 가 생겼다. \; 대신 + 로 끝내면 find 가 1로 끝나 맥 cp 의 이 실패가 종료 코드로 올라오지만, \; 형태에서는 복사한 파일을 직접 가리키는 검사만 이 실패를 잡았다.
대신 쓸 수 있는 여섯 가지와 어디서 갈리는가
기준은 gcp -p --parents -t ref 파일... 의 결과다. 대상은 8개 파일이고, 공백이 든 이름과 한글 이름, 대시로 시작하는 이름, 실행 권한 파일, 읽기 전용 파일, 하드 링크, 심볼릭 링크를 섞었다. 기준 결과는 폴더를 포함해 14개 항목이다. 항목마다 이름·종류·권한·수정 시각·크기·링크 대상, 일반 파일은 내용의 SHA-256 앞 12자를 비교했다. 폴더의 수정 시각과 크기, 링크의 수정 시각은 비교하지 않았고, 하드 링크가 같은 inode 를 공유하는지도 보지 않았다.
| 방법 | 기준과 같은가 | 다른 점 |
|---|---|---|
함수 cp_parents(아래) |
같음 | |
ditto -- 원본 대상/원본 을 파일마다 |
같음 | 대상 경로를 직접 써야 함 |
rsync -aRL |
같음 | |
pax -rw -L -p p |
같음 | |
cpio -pdmL |
같음 | |
rsync -aR |
다름 | 심볼릭 링크를 링크로 남김(기준은 내용을 복사) |
tar -cf - … | tar -xf - |
다름 | 링크를 링크로 남기고 한글 이름의 바이트가 바뀜 |
링크 차이는 대부분 옵션이 정한다(ditto 는 옵션 없이 링크를 따라가 기준과 같았다). 맥의 man cp 는 -R 이 없으면 심볼릭 링크를 항상 따라간다고 적고, 기준 gcp 와 함수는 링크 link.txt 를 4바이트 일반 파일로 복사했다. rsync -aR 와 tar, pax, cpio 는 기본으로 링크를 링크(d.txt 를 가리키는 5바이트)로 남겼고, rsync -aRL·pax -L·cpio -L 로 따라가게 하면 기준과 같아졌다. tar -h 로 링크를 따라가게 해도 아래 한글 문제는 남았다.
한글 이름은 tar 에서만 어긋났다. K/한글/파일.txt 를 tar 파이프로 옮기자 이름의 바이트가 NFC(조합형) 6·10바이트에서 NFD(분해형) 18·19바이트로 바뀌었다. 같은 파일을 함수로 복사하면 NFC 그대로였다. 화면에서는 같은 글자로 보이므로 파일 이름을 문자열로 비교하는 스크립트에서 뒤늦게 문제가 될 수 있다.
그대로 붙여 쓸 함수
링크를 gcp 처럼 따라가고 한글 이름도 그대로 두면서 실패를 종료 코드로 올리려면 폴더를 만들고 cp -p 를 하는 아래 함수를 쓸 수 있다. 원본 폴더의 권한까지 맞춰야 하면 다음 절을 먼저 보라.
cp_parents() { # 사용법: cp_parents 대상폴더 파일...
local dest=$1 f
[ -d "$dest" ] || { echo "cp_parents: $dest 는 폴더가 아니다" >&2; return 2; }
shift
for f in "$@"; do
mkdir -p -- "$dest/$(dirname -- "$f")" || return 1
cp -p -- "$f" "$dest/$(dirname -- "$f")/" || return 1
done
}
# find 와 함께: 파일 이름에 공백이 있어도 되고, 실패하면 종료 코드 1
find src -name '*.txt' -print0 |
while IFS= read -r -d '' f; do cp_parents dest "$f" || exit 1; done
시험한 동작은 이렇다.
- 경로 모양이
src/a/one.txt,./src/a/one.txt,src//a/one.txt일 때 결과가dest/src/a/one.txt로gcp와 같았고, 절대 경로는gcp와 마찬가지로dest아래에 전체 경로를 다시 만들었다. - 없는 파일은
cp: … No such file or directory와 종료 코드 1, 대상 폴더가 없으면 2였다. 파일 셋 중 둘째가 없으면 첫째만 복사하고 멈췄다. - 쓰기 불가 폴더를 대상으로 하면 위
find줄이 1로 끝났다.set -euo pipefail아래에서 없는 파일을 주면 스크립트가 1로 멈췄고, zsh 에서도 같은 함수가 파일 두 개를 복사했다. - 일치하는 파일이 하나도 없으면 함수 줄은 0(
PIPESTATUS0 0)으로 끝나고,find -exec gcp --parents {} dest \;도 0으로 끝났으며 둘 다 항목이 생기지 않았다.
원본 폴더의 권한은 방법마다 갈린다
앞 비교의 입력 폴더는 모두 755 라서 폴더 권한 보존 여부는 그 표로 구별되지 않는다. 그래서 권한 700 인 P/priv/f.txt 하나를 방법마다 복사해 대상 P/priv 의 권한을 쟀다. gcp --parents, ditto, rsync -aR, rsync -aRL 은 700 을 따라갔고, 함수와 tar, pax, cpio 는 755 로 만들었다.
폴더 권한까지 맞춰야 하면 앞 표와 이 시험을 모두 통과한 ditto(파일마다)나 rsync -aRL 을 고른다. 함수 방식은 파일마다 mkdir·dirname·cp 를 따로 실행하므로 파일이 아주 많을 때의 속도는 재지 않았다.
시험하지 못한 것
리눅스에서 도는 GNU cp 는 쓰지 못했고 이 맥의 gcp 9.11 이 대신했다. 8개 파일 밖의 입력(줄바꿈이 든 이름, 확장 속성, ACL, 아주 많은 파일, 외부 디스크나 네트워크 볼륨)과 다른 macOS 버전, rsync 를 Homebrew 3.x 로 바꾼 경우는 시험하지 않았다. 원본 경로에 .. 가 든 경우(함수가 dest 밖에 쓰는지), 700 폴더가 여러 겹이거나 같은 폴더에 파일을 나눠 복사하는 경우(권한 시험은 한 단계 폴더와 파일 하나였다), LC_ALL=C 밖의 로케일에서 tar 의 이름 변환도 시험하지 않았다. 공개 기록 두 건은 증상을 확인하는 근거로만 썼고 검색량은 재지 않았다.
세 줄 요약
- 이 맥(26.6.2)의 cp 는 –parents·-t·-u·–archive 등을 illegal option 과 종료 코드 64로 거부하고, 오류를 버린 find -exec … ; 는 종료 코드 0으로 끝나며 dest 는 비어 있었다(끝을 + 로 바꾸면 1).
- 8개 파일 대조에서 cp_parents 함수와 rsync -aRL, pax -L, cpio -L, ditto 는 gcp -p –parents 와 같았고, rsync -aR 은 링크를 링크로 남겼으며 tar 는 한글 이름을 분해형으로 바꿨다.
- 700 권한 폴더는 ditto·rsync 가 따라갔고 함수·tar·pax·cpio 는 755 로 만들었으므로, 폴더 권한까지 필요하면 rsync -aRL 이나 ditto 를 쓰고 복사 뒤에는 결과 파일을 직접 확인한다.
To help a reader who ran Linux-style cp --parents on macOS and hit an error, or whose files silently did not get copied, find the cause and pick a replacement that fits their situation (links, Hangul names, folder permissions).
The cp on this Mac (26.6.2) rejects not only --parents but also GNU options such as -t, -u, --backup and --archive with illegal option and exit code 64, and find -exec cp --parents {} dest \; with the error discarded copies nothing and still ends with exit code 0 (1 if you change the ending to +). In an eight-file comparison, a function that makes the folder and runs cp -p, rsync -aRL, pax -L, cpio -L and ditto matched GNU cp -p --parents file by file, rsync -aR left links as links, and tar changed Hangul names to the decomposed form. For a folder with permission 700, ditto and rsync followed it while the function, tar, pax and cpio made it 755.
Find where your scripts use cp --parents, replace it with the function below, and run it once with a missing file mixed in to see that the exit code is not 0.
Contents
One-line summary: The cp on this Mac (26.6.2) rejects –parents with exit code 64 and a find -exec … ; that discards the error ends with exit code 0, so use a function that makes the folder and runs cp -p, or rsync -aRL.
If you paste a script that copied files with cp --parents src/a/b/c.txt dest, keeping the folder structure on Linux, into a Mac terminal, it fails as below. This article shows, by running things on this Mac, why the error happens, what happens when the error is discarded, and where the ways of getting the same result on a Mac (six methods, seven rows in the table once option variants are counted) part ways.
cp: illegal option -- -
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-aclpSsvXx] source_file target_file
The environment I checked is macOS 26.6.2 arm64 with /bin/cp, gcp from Homebrew’s GNU coreutils 9.11 as the reference, the rsync that ships with the Mac (openrsync, protocol 29), and bash 3.2.57, on 2026-10-27. I ran each test script twice with LC_ALL=C, and the second result matched the first. The same symptom and error text are already on public record: there is the Stack Overflow question “cp –parents option on mac” (asked in 2012, score 74, 37,521 views as of 2026-10-27), and rmlint issue 275 (2018) reports the same cp: illegal option -- -, and a comment explains that it happened because --remove-destination is an option BSD does not have.
A few terms. According to the GNU manual, cp --parents appends the source file’s name to the target folder and creates any missing intermediate folders; for example cp --parents a/b/c existing_dir creates existing_dir/a/b/c. GNU is the command collection many Linux distributions use, and the Mac’s commands are BSD-family, so their options differ. An exit code is the number a command leaves when it ends, and 0 means success. PIPESTATUS is a bash array holding the exit code of each command in a pipe. A symbolic link is a shortcut file that points to another file.
Options the Mac cp rejects and options it accepts
--parents gave illegal option -- - and exit code 64. The message does not say which option it is. Only the single character - shows, so it is hard to tell that a whole long option was rejected. --target-directory, --backup, --update, --preserve=all, --no-clobber, --reflink=auto and --archive also gave 64 in the same way, and the short -t, -T and -u also gave illegal option and 64.
| Option | Mac /bin/cp |
gcp 9.11 |
|---|---|---|
--parents |
64 | 0 |
-t folder, --target-directory |
64 | 0 |
-T, -u, --update, --backup, --no-clobber |
64 | 0 |
--preserve=all, --reflink=auto, --archive |
64 | 0 |
-a, -p, -n, -R, -r, -v |
0 | 0 |
-c (clonefile copy), -X |
0 | 1 (invalid option) |
The last row goes the other way: -c and -X, which exist only on the Mac, are rejected by the GNU side. The Mac options alone cannot do in one step what GNU’s --parents does, so you have to make the folders separately.
Discard the error and nothing is copied, yet it reports success
In scripts this line is usually attached to find. Because the error screen was messy, I attached 2>/dev/null and ran it on this Mac. The targets were two files, src/a/b/c.txt and src/a/d.txt.
find src -name '*.txt' -exec cp --parents {} dest \; 2>/dev/null
echo $? # 0
find dest -mindepth 1 | wc -l # 0
When I printed the exit code of cp separately inside find, it had failed with 64 for every file, yet find ended with 0 and dest had no entries at all. When I changed the ending to + (-exec cp --parents {} dest +), find returned 1. xargs -0 -I{} cp --parents {} dest also ended with xargs at 1, so PIPESTATUS was 0 1. In all three forms dest was empty. So, in the \; form, a check that only looks at ls dest after copying passed because the folder already existed, and only a check that looks directly at the file with [ -e dest/src/a/d.txt ] reported “not copied”.
Switching the same find ... \; to gcp produced src/a/b/c.txt and src/a/d.txt under dest. Ending with + instead of \; makes find end with 1, so the Mac cp failure comes up as an exit code, but in the \; form only a check that points directly at the copied file caught the failure.
Six replacements and where they differ
The reference is the result of gcp -p --parents -t ref files.... There are eight target files, mixing a name with a space, a Hangul name, a name starting with a dash, an executable file, a read-only file, a hard link and a symbolic link. The reference result has 14 entries including folders. For each entry I compared name, type, permissions, modification time, size and link target, and for regular files the first 12 characters of the content’s SHA-256. I did not compare the modification time and size of folders or the modification time of links, and I did not check whether hard links share the same inode.
| Method | Same as reference? | Difference |
|---|---|---|
Function cp_parents (below) |
Same | |
ditto -- source target/source per file |
Same | You must write the target path yourself |
rsync -aRL |
Same | |
pax -rw -L -p p |
Same | |
cpio -pdmL |
Same | |
rsync -aR |
Different | Leaves the symbolic link as a link (the reference copies the contents) |
tar -cf - … | tar -xf - |
Different | Leaves links as links, and the bytes of the Hangul name change |
The link difference is mostly decided by an option (ditto followed links without any option and matched the reference). The Mac’s man cp says symbolic links are always followed unless -R is set, and the reference gcp and the function copied the link link.txt as a 4-byte regular file. rsync -aR, tar, pax and cpio left it as a link (5 bytes pointing to d.txt) by default, and making them follow it with rsync -aRL, pax -L and cpio -L made them match the reference. Even with tar -h following the links, the Hangul problem below remained.
Only tar went wrong on the Hangul name. When I moved K/한글/파일.txt through a tar pipe, the name’s bytes changed from NFC (composed form) 6 and 10 bytes to NFD (decomposed form) 18 and 19 bytes. Copying the same file with the function left it NFC. The two look like the same characters on screen, so this can cause trouble later in scripts that compare file names as strings.
A function you can paste as it is
To follow links the way gcp does, leave Hangul names alone and still bring failures up as an exit code, you can use the function below, which makes the folder and runs cp -p. If the permissions of the source folders must match as well, read the next section first.
cp_parents() { # usage: cp_parents target_folder file...
local dest=$1 f
[ -d "$dest" ] || { echo "cp_parents: $dest is not a folder" >&2; return 2; }
shift
for f in "$@"; do
mkdir -p -- "$dest/$(dirname -- "$f")" || return 1
cp -p -- "$f" "$dest/$(dirname -- "$f")/" || return 1
done
}
# together with find: names with spaces are fine, and a failure gives exit code 1
find src -name '*.txt' -print0 |
while IFS= read -r -d '' f; do cp_parents dest "$f" || exit 1; done
Here is the behavior I tested.
- When the path looked like
src/a/one.txt,./src/a/one.txtorsrc//a/one.txt, the result wasdest/src/a/one.txt, the same asgcp, and an absolute path made the whole path again underdest, asgcpdoes. - A missing file gave
cp: … No such file or directoryand exit code 1, and a missing target folder gave 2. If the second of three files was missing, only the first was copied and it stopped. - With a non-writable folder as the target, the
findline above ended with 1. Underset -euo pipefaila missing file stopped the script with 1, and in zsh the same function copied two files. - When no file matched, the function line ended with 0 (
PIPESTATUS0 0) andfind -exec gcp --parents {} dest \;also ended with 0, and neither created any entry.
Source folder permissions differ by method
The input folders in the comparison above were all 755, so that table cannot tell whether folder permissions are preserved. So I copied a single P/priv/f.txt, where priv has permission 700, with each method and measured the permission of the target P/priv. gcp --parents, ditto, rsync -aR and rsync -aRL followed the 700, while the function, tar, pax and cpio made it 755.
If folder permissions must match, pick ditto (per file) or rsync -aRL, which passed both the table above and this test. The function runs mkdir, dirname and cp separately for every file, so I did not measure its speed with a very large number of files.
What I could not test
I could not use GNU cp running on Linux, and gcp 9.11 on this Mac stood in for it. I did not test input outside the eight files (names containing newlines, extended attributes, ACLs, a very large number of files, external disks or network volumes), other macOS versions, or the case where rsync is replaced with Homebrew’s 3.x. I also did not test a source path containing .. (whether the function writes outside dest), 700 folders nested several levels deep or files copied into the same folder in separate runs (the permission test used a one-level folder and a single file), or the name conversion by tar under locales other than LC_ALL=C. I used the two public records only as evidence for the symptom and did not measure search volume.
Three-line summary
- The cp on this Mac (26.6.2) rejects –parents, -t, -u, –archive and others with illegal option and exit code 64, and a find -exec … ; that discards the error ended with exit code 0 while dest stayed empty (1 if you change the ending to +).
- In the eight-file comparison, the cp_parents function, rsync -aRL, pax -L, cpio -L and ditto matched gcp -p –parents, rsync -aR left links as links, and tar changed Hangul names to the decomposed form.
- A 700 folder was followed by ditto and rsync but became 755 with the function, tar, pax and cpio, so use rsync -aRL or ditto if you need folder permissions, and check the copied files directly after copying.