我正在编写一个脚本,想在文件系统上创建一个临时文件,但它不在我的文件系统中,/tmp
而是在其他地方,而且可能不是文件也不是目录(例如,可能是命名管道或符号链接)。关键在于,我必须自己创建它。现在,我想为我的临时文件使用一个唯一的文件名,这样以后调用该实用程序以及任何其他正在运行的代码时,就不会尝试使用相同的名称。
如果我只是在中创建一个临时文件或目录/tmp
,我可以使用mktemp
。但是 - 当我只想生成名称时我该怎么做?
我正在编写一个脚本,想在文件系统上创建一个临时文件,但它不在我的文件系统中,/tmp
而是在其他地方,而且可能不是文件也不是目录(例如,可能是命名管道或符号链接)。关键在于,我必须自己创建它。现在,我想为我的临时文件使用一个唯一的文件名,这样以后调用该实用程序以及任何其他正在运行的代码时,就不会尝试使用相同的名称。
如果我只是在中创建一个临时文件或目录/tmp
,我可以使用mktemp
。但是 - 当我只想生成名称时我该怎么做?
有点矛盾的要求!按照惯例,你要么把唯一的文件放入
$TMPDIR
(默认为 /tmp),要么把它们当作状态携带文件,在这种情况下,它们属于$XDG_STATE_HOME/yourapplicationname/
(默认为$HOME/.local/state/yourapplicationname/
)。现在,这是个坏主意!只需
mktemp
创建文件,然后用你想要的内容覆盖它即可。mktemp
创建它可以确保你实际上使用的是以前从未使用过的唯一内容¹,例如,通过中断脚本的执行或第二个脚本。无论哪种情况,您都可以使用
mktemp
带有-p
选项的 ,例如但这是一个安全的临时文件名,与真正的唯一文件名并不完全相同。为此,您通常需要一个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 脚本中,竞争条件安全舞蹈就变成:
filename="$(mktemp -p "${directory}")"
安全地“保留”文件名ln -f -s -- whatever "${filename}"
则可以原子性地正确执行此操作(通过创建一个随机命名的符号链接,然后renameat
将其替换为${filename}
)。我想这就是您想要的。首先,您在同一文件系统上安全地创建一个本地临时目录,
localtmp="$(mktemp -d -p "${directory}")"
然后在其中创建一个命名管道
mkfifo -- "${localtmp}/fifo"
,然后将其重命名为目标
mv -- "${localtmp}/fifo" "${filename}"
,最后,删除空的临时目录
rmdir -- "${directory}"
就这样!诚然,这还需要一些工作,但这就是你在 Shell 脚本中安全地随机命名任何东西的方法,所以值得记录下来 :)
那么,您想要一个保证唯一的名字,还是一个概率唯一的名字?如果是后者,那么答案是肯定的,当然,您可以用任何方式随机生成一个名字,然后继续工作。但这也留下了一种可能性,无论可能性多么小,同一个工具(甚至是不相关的工具)的另一个副本最终可能会使用同一个文件,如果它碰巧生成了完全相同的名字。
为了保证唯一性,您需要实际创建对象并验证它是否是新创建的。只要您提出要求,操作系统就可以安全地执行此操作。例如,如果已存在同名文件,则
open()
带有O_CREAT
和标志的系统调用将失败。对于和 也是O_EXCL
如此。mkfifo()
mkdir()
因此,如果您想要一个 FIFO,您可以生成一个名称,
mkfifo
如果成功则运行检查,如果失败则使用新名称重试。但是手动执行这些操作有点累。对于常规文件,mktemp
它可以为您完成这些操作,但无法创建 FIFO。但是,它可以为您创建一个唯一的目录,然后您可以在该目录中创建任何文件/符号链接/FIFO,同时知道整个目录都是唯一命名的。
所以:
如果您也无法创建中间目录,那么您需要接受发生碰撞的可能性,或者手动进行检查:
从
mktemp
手册页...你也无法保证未来。
mktemp
确实有以下选项:例如,仅获得一个临时路径名,而无需使用以下命令创建:
--dry-run
该选项不安全的原因是它不能保证两个进程不会尝试使用相同的临时路径名。鉴于该
--tmpdir
选项指定了临时目录,覆盖了默认值/tmp
,使用--tmpdir
以允许mktemp
以安全的方式创建您选择的临时路径名可能会更安全。生成唯一临时文件名的真正合乎逻辑的方法(在 Unix 中)是使用当前进程 ID 来形成名称。
例如在 shell 脚本中:
任何时候都只能运行一个具有给定 ID 的进程,因此进程 ID 在该进程的生命周期内是唯一的。
一般来说,这通常被认为对于任何临时文件具有足够的“唯一性”,因为大多数此类文件的生命周期仅等于创建和使用它们的进程的生命周期。
事实上,如果您的文件确实是临时的,并且仅在您的进程(脚本)运行时使用,那么即使存在一个具有相同名称的过时剩余文件,您也可以像使用您的进程创建的文件一样使用它,因为创建它的前一个进程不可能仍在使用它。由于当前进程现在具有相同的进程ID,因此该进程不再运行,因此该文件即使先前存在,仍然是“唯一的”,尽管它可能需要一些清理,例如,如果它是一个普通文件,则需要截断。
当然,如果给定用户在任何时候只能运行脚本的一个实例,并且他们将以这样一种方式运行它:在只有他们才能写入的私有目录中创建临时文件,那么即使在临时文件名中包含进程ID也是没有意义的,但如果您不包含进程ID,人们可能会怀疑您的代码。
其余部分是关于可能影响或不影响您的脚本的潜在安全问题的题外话:
使用“可预测”的名称(例如,依赖进程 ID 来确保唯一性的名称)并不总是被认为是安全的,特别是当创建文件的目录具有比其应有的更多的权限时(或者它是一个故意具有非私有权限的共享目录,例如
/tmp
),因为某些攻击者可能会创建具有与您预期略有不同的权限的目标文件,或者甚至是指向敏感文件的符号链接,等等,等等,等等。既然您问的是 Shell 脚本,那么确保某种任意类型的文件唯一且安全地创建为 unique的最安全方法是将其创建在一个唯一命名的目录中,因为 Shell 脚本可以安全地使用唯一名称创建目录(并且假设在
umask
设置了安全的情况下,还可以使用私有权限)。这样,您的脚本还可以安全地避免潜在的“检查(创建)时间与使用时间”(TOCTOU)攻击。如果失败,只需在路径中添加一个“版本”编号mkdir(1)
,然后重试即可。但是,攻击者仍然可能对您的脚本执行拒绝服务攻击……因此,现代习惯当然是使用
mktemp(1)
,并结合其-d
选项来实现您的目的,因为这将为您提供一个安全、可靠且独特且私密(即使在不安全的umask
)的游乐场,您可以在其中创建任何类型的文件。您可以/tmp
使用选项来避免这种情况,其中dir-p dir
当然可以只是“ ”,如果您需要的话。 它会确保结果是唯一的,并且不会与任何陈旧的剩余内容冲突。.
mktemp(1)
没有 bash 单行代码?难以置信!
-c6
6 个字母长或 6^62 种可能性。