macOS에서 mktemp --suffix가 unrecognized option 오류를 낼 때: 변수가 비는 자리와 foo.XXXXXX.txt 템플릿이 두 번째에 실패하는 이유를 직접 시험한 결과When mktemp --suffix fails with unrecognized option on macOS: where the variable goes empty, and why a foo.XXXXXX.txt template fails the second time, as tested
리눅스식 mktemp --suffix=.txt 나 mktemp foo.XXXXXX.txt 를 macOS 에서 돌려 오류를 만났거나 한 번만 되고 다음 실행에서 실패한 독자가, 원인을 가려내고 확장자가 붙은 임시 파일을 맥에서 안전하게 얻는 방법을 고르게 돕는다.
이 맥(26.6.2)의 mktemp 는 --suffix 를 거부해 unrecognized option 과 종료 코드 1을 내고, 오류를 버린 대입은 변수를 비워 경로를 /out.txt 처럼 바꾼다. foo.XXXXXX.txt 는 X 가 끝에 없어 글자 그대로의 파일을 한 번 만들고 두 번째부터 File exists 로 실패하며, 확장자는 mktemp 뒤에 mv 로 붙이거나(20회 모두 서로 다른 이름) 폴더를 만들어 그 안에 이름을 정하거나 Homebrew gmktemp 를 쓰면 된다.
스크립트에서 mktemp 에 --suffix 나 X 뒤에 글자가 붙은 템플릿을 주는 곳을 찾아, 확장자가 정말 필요한지 보고 필요하면 아래 함수나 폴더 방식으로 바꾼 뒤 같은 스크립트를 두 번 연달아 돌려 보라.
목차
한 줄 요약: 이 맥(26.6.2)의 mktemp 는 –suffix 를 종료 코드 1로 거부하고 foo.XXXXXX.txt 는 두 번째 실행에 실패하므로, 확장자는 mktemp 뒤에 mv 로 붙이거나 폴더 안에 이름을 정해 얻는다.
리눅스에서 mktemp --suffix=.txt 로 확장자가 붙은 임시 파일을 만들던 스크립트를 맥 터미널에 붙였더니 아래처럼 실패한다. 이 글은 그 오류가 왜 나는지, 오류를 지나친 스크립트에서 무슨 일이 생기는지, 확장자가 필요하면 맥에서 무엇을 쓰면 되는지를 이 맥에서 직접 돌려 보인다.
mktemp: unrecognized option `--suffix=.txt'
usage: mktemp [-d] [-p tmpdir] [-q] [-t prefix] [-u] template ...
mktemp [-d] [-p tmpdir] [-q] [-u] -t prefix
같은 증상은 공개 기록에도 있다. Apache Ozone 프로젝트의 이슈 HDDS-11755(제목 “mktemp –suffix does not work on Mac”)에는 맥에서 인수 시험이 위와 같은 mktemp: unrecognized option –suffix’로 실패했다고 적혀 있고, 이 이슈는 고쳐진 상태(Resolved, Fixed)로 표시되어 있다. 확인한 환경은 macOS 26.6.2 arm64 의/usr/bin/mktemp(shell_cmds-329), 비교용으로 Homebrew 의 GNU coreutils 9.11 에 든 gmktemp(인용한 GNU 설명서 웹 페이지는 9.12 판이고 –suffix서술은 두 판에서 같았다), bash 3.2.57이다. 시험 스크립트는LC_ALL=C` 로 두 번씩 돌려 같은 출력을 확인했다(템플릿 속 무작위 글자는 출력에서 가렸다).
용어 몇 개. mktemp 는 겹치지 않는 임시 파일(또는 폴더) 이름을 정해 실제로 만들고 그 경로를 출력하는 명령이다. 템플릿은 foo.XXXXXX 처럼 X 를 자리표시자로 쓴 이름이고, 명령이 X 를 무작위 글자로 바꾼다. 종료 코드는 명령이 끝나며 남기는 숫자로 0이 정상이다. GNU 는 많은 리눅스 배포판이 쓰는 명령 모음이고, 맥의 명령은 BSD 계열이라 옵션이 다르다.
맥의 mktemp 가 거부하는 옵션과 받아 주는 옵션
--suffix=.txt, --suffix, --version, --help 는 모두 unrecognized option 과 종료 코드 1이었고 표준 출력은 비어 있었다. 반대로 GNU 식 긴 이름 가운데 --tmpdir, --directory, --dry-run, --quiet 는 오류 없이 종료 코드 0으로 동작했다. 이 맥의 man mktemp 도 -d, --directory, -p, --tmpdir[=tmpdir], -q, --quiet, -u, --dry-run 을 적고 있고 --suffix 는 없다. 그러니 이 시험에서는 mktemp --directory 같은 GNU 식 긴 이름은 맥에서도 되고 --suffix(와 --version·--help)만 실패했다. 리눅스 스크립트의 옵션을 하나씩 의심해야 한다는 뜻이다.
GNU 설명서는 --suffix=suffix 를 “템플릿 뒤에 suffix 를 붙인다”고 적고, --suffix 를 주면 템플릿이 X 로 끝나야 하며 주지 않으면 템플릿의 마지막 X 뒤를 접미사로 본다고 적는다. 맥의 mktemp 는 이 해석을 하지 않는다. 뒤의 시험이 그 결과다.
오류를 숨기면 경로가 바뀐다
오류를 2>/dev/null 로 버리고 변수에 담은 스크립트를 시험했다.
f=$(mktemp --suffix=.txt 2>/dev/null) # 종료 코드 1, f 는 빈 문자열
echo hi > $f # bash: $f: ambiguous redirect
echo hi > "$f" # bash: : No such file or directory
bash 3.2.57 에서는 따옴표 없이 쓰면 ambiguous redirect, 따옴표를 쓰면 빈 이름이라 No such file or directory 였다. 맥 기본 셸 zsh 에서 같은 줄을 돌려 보니 두 경우 모두 no such file or directory: 와 종료 코드 1이어서 문구가 달랐다. 둘 다 파일은 만들어지지 않는다. 더 조심할 쪽은 폴더 변수다. d=$(mktemp -d --suffix=-job 2>/dev/null) 가 실패해 d 가 비면 "$d/out.txt" 는 /out.txt 로, "$d"/* 는 /* 였다(시험은 패턴 확장을 끄고 문자열만 출력했다). 시험에서는 이 경로를 출력만 하고 쓰거나 지우지 않았다. 뒤에 rm -rf "$d"/* 같은 정리 줄이 있는 스크립트라면 같은 문자열이 루트 쪽을 가리킨다는 뜻이므로, 변수가 비었는지 먼저 확인하는 습관이 필요하다.
set -e 를 켠 스크립트에서는 f=$(mktemp --suffix=.txt 2>/dev/null) 대입이 종료 코드 1로 그 자리에서 멈췄다. 같은 대입의 $( ) 안쪽 끝에 || true 를 붙이면 멈추지 않고 f=[] 로 통과했다. 오류를 달래려고 붙인 || true 가 빈 변수를 다음 줄로 넘기는 셈이다. 같은 명령을 gmktemp --suffix=.txt -p 폴더 foo.XXXXXX 로 돌리면 종료 코드 0으로 foo.<6자>.txt 가 만들어졌다.
foo.XXXXXX.txt 는 한 번만 되고 두 번째에 실패한다
--suffix 가 안 되니 템플릿에 확장자를 직접 쓰는 사람이 많다. 맥에서 mktemp /폴더/foo.XXXXXX.txt 를 두 번 돌렸다.
run 1 rc=0 stdout=[<T>/foo.XXXXXX.txt] stderr=[]
run 2 rc=1 stdout=[] stderr=[mktemp: mkstemp failed on <T>/foo.XXXXXX.txt: File exists]
첫 실행은 오류 없이 종료 코드 0이었지만 이름의 X 가 하나도 바뀌지 않았다. 이 맥의 man 은 템플릿에서 “뒤쪽의(trailing) X” 를 바꾼다고 적는다. X 뒤에 .txt 가 붙으면 바꿀 X 가 없어 글자 그대로의 파일이 하나 생기고, 두 번째 실행은 같은 이름이 이미 있어 File exists 와 종료 코드 1로 끝난다. 그래서 처음 한 번은 잘 도는 것처럼 보이다가 같은 스크립트를 다시 돌릴 때 깨진다. 같은 템플릿을 GNU gmktemp 로 두 번 돌리면 매번 다른 무작위 이름이 나왔다. X 가 이름 끝에 있는 end.XXXXXX 는 맥에서도 세 번 모두 다른 이름이었다.
-t 도 뜻이 다르다. 맥에서 mktemp -t foo 는 foo 를 접두사로 삼아 foo.<10자> 를 만들고, mktemp -t foo.XXXXXX 는 X 를 그대로 둔 채 뒤에 10자를 더 붙여 foo.XXXXXX.<10자> 가 됐다. GNU gmktemp -t foo 는 too few X's in template 'foo' 로 실패하고 gmktemp -t foo.XXXXXX 는 X 를 바꾼다. 또 TMPDIR 을 시험용 폴더로 바꿔 두어도 맥의 -t 파일은 사용자 기본 임시 폴더(/var/folders/…/T/)에 만들어졌다. man 의 DESCRIPTION 셋째 단락이 -t 는 사용자 임시 폴더 설정을 먼저 쓰고 TMPDIR 은 그것이 없을 때의 예비라고 적은 것과 같다(같은 man 의 OPTIONS 에 있는 -t 항목은 “TMPDIR if set” 이라고 적어 서로 어긋나므로, 이 글은 시험 결과를 근거로 삼는다). 인수 없이 부른 mktemp 도 -t tmp 를 준 것처럼 동작한다고 man 이 적는다. 만들어진 파일은 권한이 -rw-------, 폴더는 drwx------ 였다.
확장자가 필요할 때 쓸 수 있는 방법
확장자를 붙이는 방법 네 가지를 각각 20번 돌려 이름이 겹치는지, 파일이 몇 개 남는지 셌다.
| 방법 | 20회 결과 |
|---|---|
f="$(mktemp 폴더/a.XXXXXX).txt" |
이름 20개는 서로 달랐지만 폴더에 파일이 40개 |
mktemp 뒤에 mv 로 확장자 붙이기 |
이름 20개 모두 다름, 파일 20개, 모두 .txt |
mktemp -d 로 폴더를 만들고 안에 report.txt |
폴더 20개 모두 다름, report.txt 20개 |
Homebrew gmktemp --suffix=.txt |
이름 20개 모두 다름, 파일 20개, 모두 .txt |
첫 줄이 흔한 함정이다. $(mktemp 폴더/a.XXXXXX).txt 는 확장자가 붙은 새 이름을 만들 뿐 mktemp 가 이미 만든 확장자 없는 파일은 그대로 남아, 짝 없이 남는 파일이 20개 더 생겼다. 확장자를 붙이는 쪽은 mv 로 이름을 바꿔야 한다.
맥의 기본 도구만으로 확장자를 얻는 함수는 아래와 같다. 시험에서 mktemp_ext 폴더 log .json 은 log.<6자>.json 을 종료 코드 0, 권한 -rw------- 로 만들었고, 없는 폴더를 주면 mkstemp failed … No such file or directory 와 종료 코드 1을 내며 파일을 남기지 않았다.
mktemp_ext() { # 사용: mktemp_ext 폴더 접두사 확장자 -> 만든 파일 경로를 출력
local b; b=$(/usr/bin/mktemp "$1/$2.XXXXXX") || return 1
mv "$b" "$b$3" || { rm -f "$b"; return 1; }
printf '%s\n' "$b$3"
}
mkdir -p "$HOME/tmp" # 폴더가 먼저 있어야 한다
f=$(mktemp_ext "$HOME/tmp" log .json) || exit 1 # 실패하면 여기서 멈춘다
확장자가 사실은 필요하지 않은 경우가 많다. 프로그램이 파일 이름이 아니라 내용을 읽는다면 mktemp 폴더/job.XXXXXX 로 충분하고, 이름이 정해져 있어야 한다면 폴더를 만들고 안에 원하는 이름을 두는 방법이 이름 충돌을 가장 덜 신경 쓴다. 리눅스와 맥에서 같은 스크립트를 돌려야 한다면, 이 글의 해석으로는 --suffix 대신 위 두 방법 가운데 하나를 쓰는 편이 안전하다. 두 방법은 --suffix 를 쓰지 않으므로 이 맥에서 통과한 것과 같은 명령이다(리눅스 실행은 시험하지 않았다). Homebrew 의 coreutils 를 깔아 gmktemp 를 쓰는 방법은 그 도구가 있는 맥에서만 된다.
시험하지 못한 것
리눅스에서 도는 GNU mktemp 는 쓰지 못했고 이 맥의 gmktemp 9.11 이 대신했다. zsh(빈 변수 리다이렉션 한 줄 말고는), 다른 macOS 버전, 대소문자 구분 파일 시스템, 템플릿 속 X 개수가 적을 때의 충돌 확률, 여러 템플릿을 한 번에 준 경우, 이 글의 mv 방식이 여러 프로세스에서 동시에 도는 경우는 확인하지 못했다. 공개 이슈는 증상을 확인하는 근거로만 썼고 그 프로젝트의 수정 내용은 열어 보지 않았다.
세 줄 요약
- 이 맥(26.6.2)의 mktemp 는 –suffix·–version·–help 를 unrecognized option 과 종료 코드 1로 거부하고 –directory·–tmpdir·–dry-run·–quiet 는 받아 준다.
- foo.XXXXXX.txt 처럼 X 뒤에 글자가 붙으면 첫 실행은 글자 그대로의 파일을 만들고 두 번째에 File exists 로 실패하며, 오류를 버린 대입은 변수를 비워 경로를 /out.txt 로 바꾼다.
- 확장자는 mktemp 뒤에 mv 로 붙이거나(20회 모두 다른 이름) 폴더 안에 이름을 정하거나 gmktemp –suffix 로 얻고, $(mktemp).txt 는 짝 없는 파일이 남는다.
To help a reader who ran Linux-style mktemp --suffix=.txt or mktemp foo.XXXXXX.txt on macOS and hit an error, or saw it work once and fail on the next run, find the cause and choose a safe way to get a temp file with an extension on a Mac.
The mktemp on this Mac (26.6.2) rejects --suffix with unrecognized option and exit code 1, and an error that is thrown away leaves the variable empty so paths become /out.txt. foo.XXXXXX.txt has no X at the end, so it creates a literal file once and fails with File exists from the second run on; add the extension with mv after mktemp (20 runs, 20 different names), make a folder and name the file inside it, or use Homebrew gmktemp.
Find the places in your scripts where mktemp gets --suffix or a template with text after the X characters, check whether the extension is really needed, and if it is, switch to the function below or the folder approach and run the same script twice in a row to check.
Contents
One-line summary: The mktemp on this Mac (26.6.2) rejects –suffix with exit code 1 and foo.XXXXXX.txt fails on the second run, so add the extension with mv after mktemp or name the file inside a folder you create.
You ran mktemp --suffix=.txt, which you used on Linux to make a temp file with an extension, in a Mac terminal, and it fails like this. This article shows on this Mac why the error happens, what goes wrong in a script that skips past the error, and what to use on a Mac when you need an extension.
mktemp: unrecognized option `--suffix=.txt'
usage: mktemp [-d] [-p tmpdir] [-q] [-t prefix] [-u] template ...
mktemp [-d] [-p tmpdir] [-q] [-u] -t prefix
The same symptom is in public records. The Apache Ozone issue HDDS-11755 (titled “mktemp –suffix does not work on Mac”) says an acceptance test on a Mac failed with the same mktemp: unrecognized option –suffix’, and the issue is marked as fixed (Resolved, Fixed). The environment I checked is macOS 26.6.2 arm64 with /usr/bin/mktemp(shell_cmds-329),gmktempfrom Homebrew's GNU coreutils 9.11 for comparison (the GNU manual web page I quote is the 9.12 edition, and its–suffixdescription was the same in both editions), and bash 3.2.57. I ran each test script twice withLC_ALL=C` and got the same output both times (the random characters inside templates are masked in the output).
A few terms. mktemp is a command that picks a unique temporary file (or folder) name, actually creates it, and prints its path. A template is a name like foo.XXXXXX that uses X as a placeholder, and the command replaces the X characters with random characters. The exit code is the number a command leaves when it ends, and 0 means success. GNU is the set of commands many Linux distributions use, while the Mac’s commands are BSD-family and have different options.
Options the Mac’s mktemp rejects and options it accepts
--suffix=.txt, --suffix, --version and --help all gave unrecognized option with exit code 1 and an empty standard output. On the other hand, among GNU-style long names, --tmpdir, --directory, --dry-run and --quiet worked with exit code 0 and no error. The man mktemp on this Mac also lists -d, --directory, -p, --tmpdir[=tmpdir], -q, --quiet and -u, --dry-run, and has no --suffix. So in this test GNU-style long names such as mktemp --directory worked on the Mac, and only --suffix (along with --version and --help) failed. That means you have to doubt the options of a Linux script one by one.
The GNU manual describes --suffix=suffix as appending the suffix to the template, says the template must end in X when --suffix is given, and says that when it is not given the part after the last X in the template is treated as the suffix. The Mac’s mktemp does not make that interpretation. The tests below are the result.
Hiding the error changes the path
I tested a script that throws the error away with 2>/dev/null and stores the result in a variable.
f=$(mktemp --suffix=.txt 2>/dev/null) # exit code 1, f is an empty string
echo hi > $f # bash: $f: ambiguous redirect
echo hi > "$f" # bash: : No such file or directory
In bash 3.2.57, without quotes the result was ambiguous redirect, and with quotes it was No such file or directory because the name was empty. When I ran the same lines in zsh, the Mac’s default shell, both cases gave no such file or directory: and exit code 1, so the wording differed. In both cases no file is created. The folder variable needs more care. When d=$(mktemp -d --suffix=-job 2>/dev/null) fails and d is empty, "$d/out.txt" became /out.txt and "$d"/* was /* (the test turned pattern expansion off and printed only the strings). In the test I only printed this path and never wrote to it or deleted anything. In a script that has a cleanup line such as rm -rf "$d"/* afterwards, the same string points toward the root of the disk, so you need the habit of checking whether the variable is empty first.
In a script with set -e, the assignment f=$(mktemp --suffix=.txt 2>/dev/null) stopped right there with exit code 1. When I added || true at the end inside the $( ) of the same assignment, it did not stop and passed with f=[]. The || true added to calm the error simply hands an empty variable to the next line. Running the equivalent command as gmktemp --suffix=.txt -p folder foo.XXXXXX made foo.<6 chars>.txt with exit code 0.
foo.XXXXXX.txt works once and fails the second time
Because --suffix does not work, many people write the extension straight into the template. I ran mktemp /folder/foo.XXXXXX.txt twice on the Mac.
run 1 rc=0 stdout=[<T>/foo.XXXXXX.txt] stderr=[]
run 2 rc=1 stdout=[] stderr=[mktemp: mkstemp failed on <T>/foo.XXXXXX.txt: File exists]
The first run ended with exit code 0 and no error, but not one X in the name was replaced. The man page on this Mac says it replaces the “trailing” X characters of the template. When .txt follows the X characters, there is no X left to replace, so one file with the literal name is created, and the second run ends with File exists and exit code 1 because the same name already exists. So the script looks fine the first time and breaks when you run it again. Running the same template through GNU gmktemp twice gave a different random name each time. end.XXXXXX, where the X characters are at the end of the name, gave three different names on the Mac as well.
-t also means something different. On the Mac, mktemp -t foo uses foo as a prefix and makes foo.<10 chars>, and mktemp -t foo.XXXXXX leaves the X characters alone and adds 10 more characters after them, giving foo.XXXXXX.<10 chars>. GNU gmktemp -t foo fails with too few X's in template 'foo', and gmktemp -t foo.XXXXXX replaces the X characters. Also, even when I changed TMPDIR to a test folder, the Mac’s -t files were created in the user’s default temp folder (/var/folders/…/T/). This matches the third paragraph of the DESCRIPTION section of the man page, which says -t uses the user temp folder setting first and TMPDIR is only a fallback when that is missing (the -t entry in the OPTIONS section of the same man page says “TMPDIR if set”, so the two disagree, and this article relies on the test result). The man page also says that mktemp called with no arguments behaves as if -t tmp were given. The files it created had permissions -rw-------, and folders had drwx------.
Ways to get an extension when you need one
I ran four ways of adding an extension 20 times each and counted whether the names collide and how many files are left.
| Method | Result of 20 runs |
|---|---|
f="$(mktemp folder/a.XXXXXX).txt" |
20 different names, but 40 files in the folder |
add the extension with mv after mktemp |
20 different names, 20 files, all .txt |
make a folder with mktemp -d and put report.txt in it |
20 different folders, 20 report.txt files |
Homebrew gmktemp --suffix=.txt |
20 different names, 20 files, all .txt |
The first row is the common trap. $(mktemp folder/a.XXXXXX).txt only builds a new name with an extension, and the extensionless file that mktemp already created stays behind, so 20 extra orphan files were left. The way that adds an extension has to rename with mv.
Here is a function that gets an extension using only the Mac’s built-in tools. In the test, mktemp_ext folder log .json created log.<6 chars>.json with exit code 0 and permissions -rw-------, and when given a folder that does not exist it gave mkstemp failed … No such file or directory with exit code 1 and left no file behind.
mktemp_ext() { # usage: mktemp_ext folder prefix extension -> prints the created file path
local b; b=$(/usr/bin/mktemp "$1/$2.XXXXXX") || return 1
mv "$b" "$b$3" || { rm -f "$b"; return 1; }
printf '%s\n' "$b$3"
}
mkdir -p "$HOME/tmp" # the folder must exist first
f=$(mktemp_ext "$HOME/tmp" log .json) || exit 1 # stops here on failure
In many cases you do not actually need the extension. If the program reads the contents rather than the file name, mktemp folder/job.XXXXXX is enough, and if the name has to be fixed, making a folder and putting the name you want inside it is the way that worries least about name collisions. If you need to run the same script on Linux and on a Mac, this article’s reading is that using one of those two methods instead of --suffix is the safer choice. Neither method uses --suffix, so they are the same commands that passed on this Mac (I did not test running them on Linux). Installing Homebrew’s coreutils and using gmktemp only works on Macs that have that tool.
What I could not test
I could not use the GNU mktemp that runs on Linux, and gmktemp 9.11 on this Mac stood in for it. I did not check zsh (apart from the one-line empty-variable redirection), other macOS versions, case-sensitive file systems, the collision probability when a template has few X characters, giving several templates at once, or this article’s mv method running in several processes at the same time. I used the public issue only as evidence that the symptom exists and did not open that project’s fix.
Three-line summary
- The mktemp on this Mac (26.6.2) rejects –suffix, –version and –help with unrecognized option and exit code 1, but accepts –directory, –tmpdir, –dry-run and –quiet.
- With text after the X characters, as in foo.XXXXXX.txt, the first run creates a literal file and the second fails with File exists, and an error that is thrown away leaves the variable empty so a path turns into /out.txt.
- Get an extension by adding it with mv after mktemp (20 runs, 20 different names), by naming the file inside a folder, or with gmktemp –suffix, and note that $(mktemp).txt leaves orphan files.