mktemp 命令详解与参数实测(Ubuntu 22.04)
以 Ubuntu 22.04(coreutils 8.32)实测讲解 mktemp:模板 ≥3 连续 X 规则与报错、恒定 600/700 权限、-d/-p/--suffix/-q/-u/-t 全参数、TMPDIR 优先级与非法值行为、trap 清理惯例,并给出 mktemp 与传统 $$ 固定命名方案的实测对比。
mktemp 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 mktemp 的每个参数与工程惯例——模板「至少 3 个连续 X」规则及两种报错、文件恒 600/目录恒 700 的权限保证、
-d/-p/--suffix/-q/-u/-t逐项行为、TMPDIR 环境变量与-p的优先级、脚本 trap 清理惯例,以及 mktemp 相对传统$$固定命名的安全优势,全部命令与回显已在 Ubuntu 22.04(GNU coreutils 8.32-4.1ubuntu1.4)实测通过。
一、环境信息
本文所有命令均在以下环境实测,回显逐字照抄:
- 操作系统:Ubuntu 22.04.5 LTS(
PRETTY_NAME="Ubuntu 22.04.5 LTS") - 内核架构:x86_64
- mktemp 所属软件包:coreutils 8.32-4.1ubuntu1.4(
dpkg -S /bin/mktemp归属 coreutils) - 默认临时目录:/tmp(TMPDIR 未设置时)
- 执行身份:root
- 演示数据目录:
/root/demo/mktemp/;演示文件统一wtz前缀(wtzdemo、wtzsuf、wtzapp 等)
二、命令概览
2.1 语法与测试数据
mktemp 解决的问题是:安全地创建一个当时不存在的临时文件/目录——名字随机不可预测、创建动作原子(存在即换名重试)、权限收紧。它把名字打印到 stdout 供脚本接住使用。语法(来自实测 mktemp --help 首行):
Usage: mktemp [OPTION]... [TEMPLATE]
本文测试数据约定:模板统一 wtz 前缀(如 wtzdemo.XXXXXX、wtz.XX);指定目录用 /root/demo/mktemp/;非法目录用 /no/such/dir-wtz。每个参数小节均可独立执行复现,回显中的随机后缀每次运行会不同。
2.2 --help 全量输出(逐行照抄)
以下为 mktemp --help 的完整实测输出:
$ mktemp --help
Usage: mktemp [OPTION]... [TEMPLATE]
Create a temporary file or directory, safely, and print its name.
TEMPLATE must contain at least 3 consecutive 'X's in last component.
If TEMPLATE is not specified, use tmp.XXXXXXXXXX, and --tmpdir is implied.
Files are created u+rw, and directories u+rwx, minus umask restrictions.
-d, --directory create a directory, not a file
-u, --dry-run do not create anything; merely print a name (unsafe)
-q, --quiet suppress diagnostics about file/dir-creation failure
--suffix=SUFF append SUFF to TEMPLATE; SUFF must not contain a slash.
This option is implied if TEMPLATE does not end in X
-p DIR, --tmpdir[=DIR] interpret TEMPLATE relative to DIR; if DIR is not
specified, use $TMPDIR if set, else /tmp. With
this option, TEMPLATE must not be an absolute name;
unlike with -t, TEMPLATE may contain slashes, but
mktemp creates only the final component
-t interpret TEMPLATE as a single file name component,
relative to a directory: $TMPDIR, if set; else the
directory specified via -p; else /tmp [deprecated]
--help display this help and exit
--version output version information and exit
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Report any translation bugs to <https://translationproject.org/team/>
Full documentation <https://www.gnu.org/software/coreutils/mktemp>
or available locally via: info '(coreutils) mktemp invocation'
关键差异解读:help 头部四行就是 mktemp 的核心契约——模板末段至少 3 个连续 X;不给模板用 tmp.XXXXXXXXXX 且隐含 --tmpdir;文件创建为 u+rw(600)、目录 u+rwx(700),仅受 umask 削减而不受 umask 放宽。-p DIR 与 --tmpdir=DIR 是同一个选项的两种写法。
2.3 版本与归属信息
$ mktemp --version
mktemp (GNU coreutils) 8.32
$ dpkg -S /bin/mktemp
coreutils: /bin/mktemp
$ dpkg -s coreutils | grep -E '^(Package|Version)'
Package: coreutils
Version: 8.32-4.1ubuntu1.4
⚠️ 注意:
dpkg -S /usr/bin/mktemp会报dpkg-query: no path found matching pattern /usr/bin/mktemp——usrmerge 下 dpkg 数据库登记的是/bin/mktemp,用/bin路径反查才命中。这是 Ubuntu 22.04 上查包归属的一个常见小坑。
三、参数逐项实测
3.1 无模板:默认 /tmp/tmp.XXXXXXXXXX
不给模板时按 help 契约使用 tmp.XXXXXXXXXX(10 个 X)并隐含 --tmpdir(落在 TMPDIR 或 /tmp):
mktemp
mktemp
实测回显:
/tmp/tmp.9UZoh8RtPN
/tmp/tmp.8SO20bKlNf
💡 要点:两次调用得到两个不同名字——每次都会真实创建文件并保证当时不存在;10 个随机字符(62 进制)的碰撞概率可忽略。名字通过 stdout 返回,脚本里用 f=$(mktemp) 接住。
3.2 权限保证:文件恒 600(umask 000 也拉不宽)
这是 mktemp 相对手工建文件的核心安全属性。默认 umask 与极端 umask 000(不限制任何权限位)下对照实测:
f1=$(mktemp /tmp/wtzperm.XXXXXX); stat -c '%a %U %G %n' "$f1"
( umask 000; f2=$(mktemp /tmp/wtzperm2.XXXXXX); stat -c '%a %n' "$f2 (umask 000 下仍是 600)" )
实测回显:
600 root root /tmp/tmp.HtCOJGaTZc
600 /tmp/tmp.D84CVGTC79 (umask 000 下仍是 600)
💡 要点:mktemp 内部按 u+rw 创建且不受 umask 放宽(umask 只能减权限不能加权限,而 mktemp 的基础权限本就是最小集 600)。普通 touch 在 umask 022 下是 644、umask 000 下是 666——多用户机器上临时文件放敏感数据时,这个差异是安全边界。
3.3 模板 X 规则:至少 3 个连续 X(两种不足报错实测)
模板的最后一段路径组件必须含至少 3 个连续 X;6 个 X、3 个 X 均成功,2 个 X 与 0 个 X 报错:
mktemp /tmp/wtzdemo.XXXXXX
mktemp /tmp/wtz.XXX
mktemp /tmp/wtz.XX
echo "退出码=$?"
mktemp /tmp/wtz.conf
echo "退出码=$?"
实测回显:
/tmp/wtzdemo.3ZwDJ2
/tmp/wtz.XJQ
mktemp: too few X's in template ‘/tmp/wtz.XX’
退出码=1
mktemp: too few X's in template ‘/tmp/wtz.conf’
退出码=1
💡 要点:报错文案 too few X's in template(注意 coreutils 输出使用弯引号包裹模板)对应退出码 1。X 只在最后一个组件里生效(/tmp/wtz.XXXXXX/file 这种把 X 放中间目录的写法不行);X 越多随机空间越大,建议至少 6 个。
3.4 X 不在末尾:成功且隐含 --suffix
模板不以 X 结尾时不报错——此时按 help 所述 --suffix is implied,X 之后的部分被当作固定后缀保留:
mktemp /tmp/wtz.XXXX.conf
echo "退出码=$?"
实测回显:
/tmp/wtz.Daa.conf
退出码=0
💡 要点:wtz.XXXX.conf 生成了 wtz.Daa.conf——随机部分在中间,.conf 作为后缀保留。需要「名字带固定后缀」的场景(如日志工具按后缀识别文件类型)有两种写法:模板尾部留 X + 显式 --suffix(见 3.7),或把固定文本写在 X 后面(本节写法)。
3.5 -d:创建临时目录(权限恒 700)
-d(directory)创建临时目录而非文件,权限按契约 u+rwx 即 700:
mktemp -d
mktemp -d -p /root/demo/mktemp wtzdir.XXXXXX
d=$(mktemp -d)
stat -c '%a %U %G %n' "$d"
实测回显:
/tmp/tmp.4FscTIhcZE
/root/demo/mktemp/wtzdir.KTGEdi
700 root root /tmp/tmp.KtOH8QO5Cj
💡 要点:临时目录 700 意味着只有当前用户能进入——在其中创建的任何子文件继承目录的保护边界。批量中间产物(解压区、构建目录)的标准做法就是 d=$(mktemp -d) + cd "$d" + trap 清理(见 4.2)。
3.6 -p:指定基础目录(优先级高于 TMPDIR)
-p DIR(等价 --tmpdir=DIR)把模板解释为相对 DIR 创建。目录不存在时失败:
mktemp -p /root/demo/mktemp wtzfile.XXXXXX
mktemp -p /no/such/dir-wtz wtzfile.XXXXXX
echo "退出码=$?"
TMPDIR=/root/demo/mktemp mktemp -p /tmp wtzp.XXXXXX
实测回显:
/root/demo/mktemp/wtzfile.2nlkhy
mktemp: failed to create file via template ‘/no/such/dir-wtz/wtzfile.XXXXXX’: No such file or directory
退出码=1
/tmp/wtzp.BKJWrx
💡 要点:报错文案 failed to create file via template 对应退出码 1,模板被拼成了完整路径展示。最后一行是优先级验证:即使 TMPDIR 指向演示目录,-p /tmp 仍落在 /tmp——显式 -p 优先于 TMPDIR 环境变量。-p 不带值时回落到 $TMPDIR(无则 /tmp),与无参数默认行为衔接。
3.7 --suffix:模板后缀分离写法
--suffix=SUFF 在模板(含随机部分)之后追加固定后缀,后缀不得含斜杠:
mktemp /tmp/wtzsuf.XXXXXX --suffix=.conf
mktemp /tmp/wtzcfg.XXXXXX --suffix=.log
mktemp -p /root/demo/mktemp wtzapp.XXXXXX --suffix=.log
实测回显:
wtzsuf.spdqPz.conf
/tmp/wtzcfg.yunDpi.log
/root/demo/mktemp/wtzapp.zspjPD.log
💡 要点:注意第一行的回显没有 /tmp 前缀——模板写成相对名时输出也是相对名(在当前目录创建);要固定位置就用 -p 或把目录写进模板(第三行为 -p + --suffix 联用的标准形态)。这与 3.4 的「X 后写死」效果相同,但 --suffix 的意图更显式。
3.8 -q:静默模式(只压创建失败,不压模板错误)
-q(quiet)按 help 所述压制「file/dir-creation failure」诊断。三类失败对照实测:
mktemp -q -p /no/such/dir-wtz wtz.XXXXXX
echo "创建失败 -q 退出码=$?"
mktemp -q /tmp/wtz.XX
echo "模板错误 -q 退出码=$?"
TMPDIR=/no/such/dir-wtz mktemp -q
echo "TMPDIR 无效 -q 退出码=$?"
实测回显:
创建失败 -q 退出码=1
mktemp: too few X's in template ‘/tmp/wtz.XX’
模板错误 -q 退出码=1
TMPDIR 无效 -q 退出码=1
💡 要点:-q 生效范围比直觉窄——-p 目录不存在、TMPDIR 无效这类创建失败完全静默(无任何输出、退出 1);而 too few X's 这类模板/用法错误照常打印。脚本里「错误自己处理、不要 stderr 噪音」时用 -q,但别指望它吞掉模板笔误的提示。
3.9 -u:dry-run 只取名不创建(unsafe)
-u(dry-run)只打印一个候选名,不做创建动作,help 标注 unsafe:
mktemp -u /tmp/wtz-dry.XXXXXX
ls -d /tmp/wtz-dry.*
echo "ls 验证(应报 No such file)退出码=$?"
实测回显:
/tmp/wtz-dry.0AZWN0
ls: cannot access '/tmp/wtz-dry.*': No such file or directory
ls 验证(应报 No such file)退出码=2
💡 要点:名字输出了但文件确实没创建(ls 报 No such file)。unsafe 的原因:拿名字到自己 touch 之间名字可能被别人抢占(TOCTOU 竞争),安全保证全部失效。仅用于「预览将生成的名字形态」,生产脚本一律用真实创建。
3.10 -t:废弃的单组件模板(仍可用)
-t 把模板当作相对目录($TMPDIR → -p 指定目录 → /tmp)的单个文件名组件处理,help 已标注 deprecated:
mktemp -t wtz.XXXXXX
echo "mktemp -t 退出码=$?"
实测回显:
/tmp/wtz.m5yYxu
mktemp -t 退出码=0
💡 要点:行为上被 -p DIR 模板(可含斜杠、只创建末段)取代,新脚本不应再写 -t。保留实测记录是为了读懂老脚本——老代码里 mktemp -t app.XXXXXX 等价于现代写法 mktemp -p "${TMPDIR:-/tmp}" app.XXXXXX。
3.11 TMPDIR 环境变量:默认位置的决定者
无参数/无 -p 时,创建位置由 TMPDIR 决定,无则 /tmp。TMPDIR 非法时报错:
TMPDIR=/root/demo/mktemp mktemp
TMPDIR=/no/such/dir-wtz mktemp
echo "TMPDIR 无效退出码=$?"
实测回显:
/root/demo/mktemp/tmp.BitcB8wzsb
mktemp: failed to create file via template ‘/no/such/dir-wtz/tmp.XXXXXXXXXX’: No such file or directory
TMPDIR 无效退出码=1
💡 要点:TMPDIR 把默认模板 tmp.XXXXXXXXXX 整体搬到了指定目录(回显第一行落在演示目录)。失效的 TMPDIR 不静默回落 /tmp 而是直接失败退出 1——有些工具(如 pip)在 TMPDIR 满/失效时集体报错,根因排查就从这一行为入手。优先级链总结:显式 -p DIR > TMPDIR > /tmp(3.6 已验证第一环)。
3.12 相对路径模板:创建在当前目录
模板不含目录成分时,文件创建在当前工作目录:
cd /root/demo/mktemp
mktemp wtzrel.XXXXXX
ls -l wtzrel.*
实测回显:
wtzrel.g0IVUN
wtzrel.g0IVUN
💡 要点:输出名没有前导目录,说明落点就是 cwd。小空间盘(如 /tmp 与工作盘不同分区)想省拷贝、或需要临时文件与目标同分区以便原子 mv 时,用相对模板直接在工作目录建临时件是惯用技巧。
3.13 trap 清理惯例:退出路径全覆盖
mktemp 的标准搭配是 trap ... EXIT——无论脚本正常结束、报错退出还是被信号打断,临时文件都被清理。完整实测脚本与运行结果:
#!/bin/bash
f=$(mktemp /tmp/wtzdemo.XXXXXX) || exit 1
trap 'rm -f "$f"; echo "[trap 触发] 已清理 $f"' EXIT
echo "脚本处理中, 临时文件: $f"
ls -l "$f"
echo "脚本业务逻辑执行完毕, 正常退出"
实测回显:
脚本处理中, 临时文件: /tmp/wtzdemo.ZJSEUa
-rw------- 1 root root 19 Oct 16 09:54 /tmp/wtzdemo.ZJSEUa
脚本业务逻辑执行完毕, 正常退出
[trap 触发] 已清理 /tmp/wtzdemo.ZJSEUa
💡 要点:ls -l 直接可见权限 -rw-------(600)与 trap 创建的临时文件;EXIT 陷阱在正常退出时也触发(bash 的 EXIT 陷阱覆盖一切退出路径,包括 set -e 中断与 kill 默认终止)。多临时件时把清理写进同一个 trap 函数,注意 mktemp 失败时 || exit 1 提前退出避免 trap 引用空变量。
3.14 技术拓展:mktemp 与传统 $$ 固定命名对比
老脚本常见 临时文件=/tmp/app.$$.log 的固定命名方案,与 mktemp 逐项实测对比($$ 为脚本 PID,本例 28743):
printf "fixed content\n" > /tmp/wtz-app-fixed-$$.log
stat -c "%a %n" /tmp/wtz-app-fixed-$$.log
printf "run1 data\n" > /tmp/wtz-app-collide-$$.log
printf "run2 data\n" > /tmp/wtz-app-collide-$$.log
cat /tmp/wtz-app-collide-$$.log
f=$(mktemp /tmp/wtz-safe.XXXXXX)
stat -c "%a %n" "$f"
实测回显:
===== 传统 28743 固定命名:可预测 + 默认权限受 umask 影响
644 /tmp/wtz-app-fixed-28743.log
===== 28743 固定名同名冲突:第二次覆盖第一次
run2 data
===== mktemp 对照:不可预测名 + 恒 600 权限
600 /tmp/wtz-safe.C0IcXf
safe content
| 对比维度 | /tmp/app.$$ 固定命名 | mktemp |
|---|---|---|
| 名字可预测性 | PID 可枚举,攻击者可预占 | 随机字符,不可预测 |
| 同名冲突 | 重跑/并发时直接覆盖旧数据(实测 run1 被 run2 覆盖) | 原子创建,已存在则换名重试,永不覆盖 |
| 文件权限 | 受 umask 影响(默认 644,组/其他可读) | 恒 600(目录 700),实测 umask 000 仍 600 |
| 符号链接攻击 | 可被预置同名软链接劫持写入 | 创建时保证「当时不存在」,链接攻击无效 |
| 脚本写法 | 一行赋值,但安全责任自负 | 一行赋值 + trap 清理,安全内建 |
一句话结论:$$ 方案在同一脚本重跑与多用户机器两个场景都实测翻车(覆盖 + 644),mktemp 用同样的代码量把三个问题一起解决,没有任何理由再用 $$ 命名临时文件。
四、实战示例
4.1 场景一:临时文件 + trap 清理(标准骨架)
脚本级临时文件的最小安全骨架,覆盖正常/异常退出:
#!/bin/bash
f=$(mktemp /tmp/wtzdemo.XXXXXX) || exit 1
trap 'rm -f "$f"; echo "[trap 触发] 已清理 $f"' EXIT
echo "脚本处理中, 临时文件: $f"
ls -l "$f"
echo "脚本业务逻辑执行完毕, 正常退出"
实测回显:
脚本处理中, 临时文件: /tmp/wtzdemo.ZJSEUa
-rw------- 1 root root 19 Oct 16 09:54 /tmp/wtzdemo.ZJSEUa
脚本业务逻辑执行完毕, 正常退出
[trap 触发] 已清理 /tmp/wtzdemo.ZJSEUa
要点:mktemp || exit 1 确保拿不到临时件就退出(磁盘满、TMPDIR 坏时快速失败);trap 单引号包裹使 "$f" 延迟到触发时展开。此骨架可直接作为脚手架复用。
4.2 场景二:临时目录工作区(构建/解压类脚本)
批量中间产物放临时目录,结束整体删除:
#!/bin/bash
d=$(mktemp -d) || exit 1
trap 'rm -rf "$d"' EXIT
cd "$d"
echo "工作目录: $d"
echo "demo" > wtz-work.txt
cat wtz-work.txt
实测回显:
工作目录: /tmp/tmp.Q3GdXRSPst
demo
要点:mktemp -d 的目录 700(3.5 实测),工作区内的文件外界不可见;rm -rf "$d" 清理整个目录树,比逐个 rm -f 临时文件更不易漏。变量 $d 在 trap 里同样单引号延迟展开,脚本退出时工作区随 trap 整体消失。
4.3 场景三:带固定后缀的临时文件(工具按类型识别)
下游工具按后缀识别文件类型时,用 --suffix 生成带后缀的临时名:
mktemp /tmp/wtzcfg.XXXXXX --suffix=.log
mktemp -p /root/demo/mktemp wtzapp.XXXXXX --suffix=.log
实测回显:
/tmp/wtzcfg.yunDpi.log
/root/demo/mktemp/wtzapp.zspjPD.log
要点:随机性保留在 .log 之前,既有 mktemp 的唯一性/600 权限保证,又满足「必须叫 .log」的工具约定;多分区场景把 -p 指到目标盘可让后续 mv 保持原子性。
五、注意事项与常见问题
Q1:mktemp: too few X's in template 报错,模板到底怎么写?
模板最后一段路径组件至少要有 3 个连续 X:wtz.XXX 合法、wtz.XX 与 wtz.conf 都报此错且退出 1(3.3 实测)。固定后缀写法有两种:X 之后直接写(wtz.XXXX.conf,3.4)或显式 --suffix=.conf(3.7)。另外 X 只认最后一个组件,/tmp/dir.XXXXXX/file 无效。
Q2:mktemp 创建的文件权限为什么是 600,能改成 644 吗?
600 是设计保证(文件 u+rw、目录 u+rwx,且 umask 只能再减不能加,3.2 实测 umask 000 下仍 600)。要 644 就在创建后 chmod 644 "$f"——但先想清楚为什么:临时文件默认最小权限是安全特性,放敏感数据(密码、密钥、导出的用户数据)时 600 恰恰是必须的,放宽权限应显式、有意识地做。
Q3:TMPDIR、-p、绝对路径模板三个都写了,听谁的?
优先级:模板是绝对路径时直接用绝对路径;否则显式 -p DIR 优先于 TMPDIR(3.6 实测 TMPDIR 指向 A、-p /tmp 时落在 /tmp);再否则 TMPDIR(3.11,注意 TMPDIR 失效会直接报错退出 1 而不是回落 /tmp);都没有用 /tmp。跨环境脚本建议全部显式 -p,不依赖环境变量。
Q4:脚本里怎么正确接住 mktemp 的名字并保证清理?
两件套:f=$(mktemp ...) || exit 1 接住并处理失败;trap 'rm -f "$f"' EXIT 覆盖所有退出路径(3.13 实测正常退出也触发)。注意两点:mktemp 失败时退出码 1 且 -q 可静默创建失败类错误(3.8);trap 必须在拿到变量之后注册,变量为空时 rm -f "" 无害但仍建议 || exit 1 前置。
Q5:-u dry-run 生成的名字能用吗?
不建议。-u 只打印名字不创建(3.9 实测 ls 报 No such file),help 标注 unsafe:从拿到名字到真正创建文件之间,同名字可能被其他进程抢占,mktemp 的原子性保证全部作废。唯一合理用途是预览名字形态或测试模板合法性。
Q6:mktemp -t 报废弃,老脚本要改吗?
-t 在 coreutils 8.32 仍可用(3.10 实测退出 0、落点 $TMPDIR/-p/-tmp 链),但 help 已标注 deprecated。新代码用 -p DIR 模板 替代(语义更明确、模板可含斜杠);存量脚本里 mktemp -t app.XXXXXX 与 mktemp -p "${TMPDIR:-/tmp}" app.XXXXXX 行为等价,读到时能对上即可。
六、参考链接
- mktemp(1) 手册(Ubuntu 22.04 jammy):https://manpages.ubuntu.com/manpages/jammy/en/man1/mktemp.1.html
- GNU Coreutils 手册(mktemp invocation):https://www.gnu.org/software/coreutils/manual/html_node/mktemp-invocation.html
- coreutils 软件包页(Debian):https://packages.debian.org/jammy/coreutils
- bash(1) 手册(trap/EXIT 陷阱语义):https://manpages.ubuntu.com/manpages/jammy/en/man1/bash.1.html
- 本机完整文档(coreutils 自带 info 页):
info '(coreutils) mktemp invocation'