AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 794236
Accepted
einpoklum
einpoklum
Asked: 2025-04-27 17:08:57 +0800 CST2025-04-27 17:08:57 +0800 CST 2025-04-27 17:08:57 +0800 CST

生成唯一文件名的惯用方法?

  • 772

我正在编写一个脚本,想在文件系统上创建一个临时文件,但它不在我的文件系统中,/tmp而是在其他地方,而且可能不是文件也不是目录(例如,可能是命名管道或符号链接)。关键在于,我必须自己创建它。现在,我想为我的临时文件使用一个唯一的文件名,这样以后调用该实用程序以及任何其他正在运行的代码时,就不会尝试使用相同的名称。

如果我只是在中创建一个临时文件或目录/tmp,我可以使用mktemp。但是 - 当我只想生成名称时我该怎么做?

shell-script
  • 6 6 个回答
  • 445 Views

6 个回答

  • Voted
  1. Marcus Müller
    2025-04-27T17:32:28+08:002025-04-27T17:32:28+08:00

    有点矛盾的要求!按照惯例,你要么把唯一的文件放入$TMPDIR(默认为 /tmp),要么把它们当作状态携带文件,在这种情况下,它们属于$XDG_STATE_HOME/yourapplicationname/(默认为$HOME/.local/state/yourapplicationname/)。

    重点是——我会亲自进行创作。

    现在,这是个坏主意!只需mktemp创建文件,然后用你想要的内容覆盖它即可。mktemp创建它可以确保你实际上使用的是以前从未使用过的唯一内容¹,例如,通过中断脚本的执行或第二个脚本。

    无论哪种情况,您都可以使用mktemp带有-p选项的 ,例如

    filename="$(mktemp -p "${XDG_STATE_HOME:-$HOME/.local/state}/einpoklumsscript/")"
    

    但这是一个安全的临时文件名,与真正的唯一文件名并不完全相同。为此,您通常需要一个UUID(通用唯一标识符),您可以使用 生成一个uuidgen。但是:与 不同mktemp,这并非竞争安全的。看来您想要 mktemp,真的!


    ¹ 出于您的技术兴趣,这可能是创建即使在运行脚本的多个实例时也不会发生冲突的文件名的唯一方法:

    mktemp通过调用来创建该临时文件openat(AT_FDCWD, "/path/to/random/filename", O_RDWR|O_CREAT|O_EXCL)。

    令人兴奋的部分是O_RDWR|O_CREAT|O_EXCL:

    • O_RDWR确保仅当结果可读可写时才创建文件。
    • O_CREAT 允许通过打开文件来创建文件,
    • O_EXCL 强制创建该文件;如果它之前存在,则openat失败,并mktemp选择不同的文件名。

    UNIX 文件系统 API 保证两个任务,即使完全同步运行,也不能O_EXCL打开同一个文件名——因此,这是操作系统提供的唯一确保文件只创建一次的方法。任何其他方法都会导致竞争条件!

    所以,正确 且 符合习惯的做法是:mktemp创建一个临时文件,然后写入数据。这才是 UNIX 的做法!不要自己创建。

    (在我的情况下是符号链接,或命名管道,或类似的东西)在我的文件系统中,[…]我将自己进行创建。

    幸运的是,这并非完全正确。除此之外openat(… , O_CREAT|O_EXCL),renameat系统调用也是原子的。

    因此,如果你在 shell 脚本中,竞争条件安全舞蹈就变成:

    1. filename="$(mktemp -p "${directory}")"安全地“保留”文件名
    2. 用您想要创建的内容自动替换该文件:
      • 如果您要替换的是符号链接,ln -f -s -- whatever "${filename}"则可以原子性地正确执行此操作(通过创建一个随机命名的符号链接,然后renameat将其替换为${filename})。我想这就是您想要的。
      • 如果您想要用命名管道(或创建工具不允许强制替换的任何其他内容)来替换它:
        首先,您在同一文件系统上安全地创建一个本地临时目录,localtmp="$(mktemp -d -p "${directory}")"然后
        在其中创建一个命名管道mkfifo -- "${localtmp}/fifo",
        然后将其重命名为目标mv -- "${localtmp}/fifo" "${filename}",
        最后,删除空的临时目录rmdir -- "${directory}"

    就这样!诚然,这还需要一些工作,但这就是你在 Shell 脚本中安全地随机命名任何东西的方法,所以值得记录下来 :)

    • 10
  2. Best Answer
    ilkkachu
    2025-04-27T21:08:45+08:002025-04-27T21:08:45+08:00

    那么,您想要一个保证唯一的名字,还是一个概率唯一的名字?如果是后者,那么答案是肯定的,当然,您可以用任何方式随机生成一个名字,然后继续工作。但这也留下了一种可能性,无论可能性多么小,同一个工具(甚至是不相关的工具)的另一个副本最终可能会使用同一个文件,如果它碰巧生成了完全相同的名字。

    为了保证唯一性,您需要实际创建对象并验证它是否是新创建的。只要您提出要求,操作系统就可以安全地执行此操作。例如,如果已存在同名文件,则open()带有O_CREAT和标志的系统调用将失败。对于和 也是O_EXCL如此。mkfifo()mkdir()

    因此,如果您想要一个 FIFO,您可以生成一个名称,mkfifo如果成功则运行检查,如果失败则使用新名称重试。但是手动执行这些操作有点累。对于常规文件,mktemp它可以为您完成这些操作,但无法创建 FIFO。

    但是,它可以为您创建一个唯一的目录,然后您可以在该目录中创建任何文件/符号链接/FIFO,同时知道整个目录都是唯一命名的。

    所以:

    dir=/path/to/worktree/
    d=$(mktemp -d -- "$dir/tmp.XXXXXX") || exit
    fifo="$d/myfifo"
    if ! mkfifo -- "$fifo"; then
        # this shouldn't happen
        printf >&2 '%s\n' "mkfifo '$fifo' failed??"
        exit 1
    fi
    
    # ...
    
    rm -f -- "$fifo" && rmdir -- "$d"
    

    如果您也无法创建中间目录,那么您需要接受发生碰撞的可能性,或者手动进行检查:

    dir=/path/to/worktree/
    until
        fifo=$(mktemp -u -- "$dir/myfifo.XXXXXX") || exit
        mkfifo -- "$fifo"
    do
        echo >&2 "oops, collision, retrying..."
    done
    printf '%s\n' "created '$fifo'"
    
    • 6
  3. Bob Goddard
    2025-04-27T17:16:50+08:002025-04-27T17:16:50+08:00

    从mktemp手册页...

    -u, --dry-run
     do not create anything; merely print a name (unsafe)
    

    你也无法保证未来。

    • 4
  4. Chester Gillon
    2025-04-27T17:27:20+08:002025-04-27T17:27:20+08:00

    mktemp确实有以下选项:

           -u, --dry-run
                  do not create anything; merely print a name (unsafe)
    

    例如,仅获得一个临时路径名,而无需使用以下命令创建:

    [mr_halfword@skylake-alma eclipse_project]$ mktemp --dry-run --tmpdir=$HOME
    /home/mr_halfword/tmp.luj0friP22
    [mr_halfword@skylake-alma eclipse_project]$ file /home/mr_halfword/tmp.luj0friP22
    /home/mr_halfword/tmp.luj0friP22: cannot open `/home/mr_halfword/tmp.luj0friP22' (No such file or directory)
    

    重点是——我会亲自进行创作。

    --dry-run该选项不安全的原因是它不能保证两个进程不会尝试使用相同的临时路径名。

    如果我只是在中创建一个临时文件或目录/tmp,我可以使用mktemp。

    鉴于该--tmpdir选项指定了临时目录,覆盖了默认值/tmp,使用--tmpdir以允许mktemp以安全的方式创建您选择的临时路径名可能会更安全。

    • 4
  5. Greg A. Woods
    2025-04-28T04:37:02+08:002025-04-28T04:37:02+08:00

    生成唯一临时文件名的真正合乎逻辑的方法(在 Unix 中)是使用当前进程 ID 来形成名称。

    例如在 shell 脚本中:

    touch tmpfile.$$
    

    任何时候都只能运行一个具有给定 ID 的进程,因此进程 ID 在该进程的生命周期内是唯一的。

    一般来说,这通常被认为对于任何临时文件具有足够的“唯一性”,因为大多数此类文件的生命周期仅等于创建和使用它们的进程的生命周期。

    事实上,如果您的文件确实是临时的,并且仅在您的进程(脚本)运行时使用,那么即使存在一个具有相同名称的过时剩余文件,您也可以像使用您的进程创建的文件一样使用它,因为创建它的前一个进程不可能仍在使用它。由于当前进程现在具有相同的进程ID,因此该进程不再运行,因此该文件即使先前存在,仍然是“唯一的”,尽管它可能需要一些清理,例如,如果它是一个普通文件,则需要截断。

    当然,如果给定用户在任何时候只能运行脚本的一个实例,并且他们将以这样一种方式运行它:在只有他们才能写入的私有目录中创建临时文件,那么即使在临时文件名中包含进程ID也是没有意义的,但如果您不包含进程ID,人们可能会怀疑您的代码。

    其余部分是关于可能影响或不影响您的脚本的潜在安全问题的题外话:

    使用“可预测”的名称(例如,依赖进程 ID 来确保唯一性的名称)并不总是被认为是安全的,特别是当创建文件的目录具有比其应有的更多的权限时(或者它是一个故意具有非私有权限的共享目录,例如/tmp),因为某些攻击者可能会创建具有与您预期略有不同的权限的目标文件,或者甚至是指向敏感文件的符号链接,等等,等等,等等。

    既然您问的是 Shell 脚本,那么确保某种任意类型的文件唯一且安全地创建为 unique的最安全方法是将其创建在一个唯一命名的目录中,因为 Shell 脚本可以安全地使用唯一名称创建目录(并且假设在umask设置了安全的情况下,还可以使用私有权限)。这样,您的脚本还可以安全地避免潜在的“检查(创建)时间与使用时间”(TOCTOU)攻击。如果失败,只需在路径中添加一个“版本”编号mkdir(1),然后重试即可。但是,攻击者仍然可能对您的脚本执行拒绝服务攻击……

    因此,现代习惯当然是使用mktemp(1),并结合其-d选项来实现您的目的,因为这将为您提供一个安全、可靠且独特且私密(即使在不安全的umask)的游乐场,您可以在其中创建任何类型的文件。您可以/tmp使用选项来避免这种情况,其中dir-p dir当然可以只是“ ”,如果您需要的话。 它会确保结果是唯一的,并且不会与任何陈旧的剩余内容冲突。.mktemp(1)

    • 1
  6. j0h
    2025-04-28T02:30:04+08:002025-04-28T02:30:04+08:00

    没有 bash 单行代码?难以置信!

    echo $(head /dev/urandom | tr -dc A-Za-z0-9 | head -c6)
    

    -c66 个字母长或 6^62 种可能性。

    • 0

相关问题

  • 在awk中的两行之间减去相同的列

  • 打印文件行及其长度的脚本[关闭]

  • 通过命令的标准输出以编程方式导出环境变量[重复]

  • 按分隔符拆分并连接字符串问题

  • MySQL Select with function IN () with bash array

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve