不能用 $$ 或 date 生成临时名,因其可预测、易重复,导致竞态条件和权限泄露;必须用 mktemp 原子创建并检查返回值,配合 trap 清理,避免硬编码 /tmp 路径。

直接结论:不用 mktemp 手动拼接 /tmp/myapp_$$ 或 $(date +%s%N),否则大概率引入竞争条件或权限泄露。
为什么不能用 $$ 或 date 生成临时名
这类做法看似简单,实则危险。进程号 $$ 可预测、可重用;date +%s%N 在高并发脚本中极易重复(尤其在容器或轻量 VM 中纳秒精度可能失效)。攻击者可在你调用 touch $tmpfile 前抢先创建同名文件,导致写入覆盖、内容篡改甚至符号链接劫持。
正确做法是依赖 mktemp 的原子性创建 —— 它内部使用 O_CREAT | O_EXCL 标志打开文件,确保“生成名 + 创建文件”一步完成,无竞态窗口。
- 永远不要写
tmpfile="/tmp/myapp_$$"+touch "$tmpfile" - 避免
mktemp -u后再touch:-u 模式只输出名字,不保证后续touch成功,且破坏原子性 - 模板中
X必须 ≥6 个(POSIX 要求),少于 6 个在部分系统(如 Alpine)会失败或降级为不安全行为
mktemp -d 创建目录时的权限陷阱
默认 mktemp -d 创建的目录权限是 0700(仅属主可读写执行),这通常合理。但若脚本需在该目录中启动子进程(如 sudo -u nobody 写日志),就会因权限拒绝失败。
此时不能直接 chmod 755 目录 —— 这会向组/其他开放遍历权限,可能暴露临时内容。更安全的做法是:
- 用
mktemp -d -p "$XDG_RUNTIME_DIR"替代/tmp,该路径默认0700且生命周期绑定用户会话 - 若必须用
/tmp,创建后立即chmod 700 "$dir"(显式加固,避免 umask 干扰) - 绝对不要用
mktemp -d /tmp/myapp.XXXXXX:这会把目录建在当前目录而非/tmp,易因 cwd 权限失控引发问题
脚本退出前必须清理,但不能靠 rm -rf /tmp/*
临时文件不清理会堆积磁盘、泄漏敏感数据;但暴力清空 /tmp 会误删其他进程正在使用的文件(如 systemd socket、X11 auth 文件),导致服务中断。
可靠清理策略是「谁创建,谁负责」+「异常也兜底」:
- 用
trap 'rm -rf "$tmpdir" "$tmpfile"' EXIT注册退出钩子,确保无论正常结束还是Ctrl+C都清理 - 若需保留日志供调试,改用
trap 'mv "$tmpdir" "/var/log/myapp/debug_$(date -Iseconds)"' ERR - 避免在 trap 中调用未导出的函数或依赖未初始化的变量 ——
trap执行环境独立于主脚本作用域
mktemp 在容器或 systemd 服务中的特殊处理
在容器里 /tmp 往往是 tmpfs,重启即丢,看似安全,但多个容器共享宿主机 /tmp(如未配置 tmpfs 挂载)时仍会冲突。systemd 服务则默认启用 PrivateTmp=yes,此时 mktemp 创建的路径实际落在 /tmp/systemd-private-xxx 下,对外不可见。
这意味着:
- 不要硬编码
/tmp路径做跨进程通信 —— 即使都用了mktemp,也可能在不同命名空间 - 调试时用
find /tmp -name "myapp*"可能找不到文件,先查systemctl show --property=PrivateTmp myservice - 若服务需与外部进程共享临时文件,显式用
-p "$XDG_RUNTIME_DIR"或挂载独立 volume,而非依赖默认行为
最易被忽略的一点:mktemp 返回值必须检查。它失败时返回非零码且输出为空,若脚本继续用空字符串当路径,后续所有 cp、echo 都会默默写到当前目录甚至根目录 —— 这类错误在线上极难复现,但后果严重。











