【问题标题】:atomic create file if not exists from bash script如果 bash 脚本不存在,则原子创建文件
【发布时间】:2012-11-29 11:53:02
【问题描述】:

在系统调用open()中,如果我用O_CREAT | O_EXCL打开,系统调用确保文件不存在时才会创建。原子性由系统调用保证。是否有类似的方法可以从 bash 脚本以原子方式创建文件?

更新: 我发现了两种不同的原子方式

  1. 使用 set -o noclobber。然后你可以原子地使用 > 操作符。
  2. 只需使用 mkdir。 Mkdir 是原子的

【问题讨论】:

  • 系统调用确保文件不存在时才会创建 Hmpf。如果文件不存在,则会创建它。如果存在,系统调用将失败。
  • 您可以尝试mktemp 创建一个文件,然后尝试mv 将其改成所需的名称。
  • 您从哪里得知“Mkdir 是原子的”?手册页没有说明它(不是)是原子的。此外,手册页并未声称 noclobber 选项是原子的。你从哪里得到的?

标签: c linux bash


【解决方案1】:

100% 纯 bash 解决方案:

set -o noclobber
{ > file ; } &> /dev/null

如果不存在名为 file 的文件,此命令将创建一个名为 file 的文件。如果有一个名为 file 的文件,则什么也不做(但返回一个非零返回码)。

使用touch 命令的优点:

  • 如果文件已存在则不更新时间戳
  • 100% 内置 bash
  • 按预期返回代码:如果file 已经存在或无法创建file,则失败;如果 file 不存在并被创建,则成功。

缺点:

  • 需要设置noclobber 选项(但在脚本中也可以,如果您小心重定向,或者之后取消设置)。

我猜这个解决方案实际上是 open 系统调用与 O_CREAT | O_EXCL 的 bash 对应物。

【讨论】:

  • “文件”是否保证是原子的?
  • (set -o noclobber;>file) &>/dev/null 的作用相同,但不影响当前 shell 中的 noclobber 选项。
  • 能否请您简要解释一下 set -o noclobber 部分的作用?如果可以取消设置,你能说一下怎么做吗?
  • @srcerer 来自参考:如果重定向操作符是 '>',并且 set 内置的 noclobber 选项已启用,如果文件名由 word 扩展产生,则重定向将失败存在并且是一个常规文件。
  • set -o noclobber cmd > file 在 bash 中似乎不是原子的。在source code 文件redir.c 中查看noclobber_open,其中评论说»希望 避免大多数竞争条件并避免在 之间替换文件的问题stat(2) 和 open(2).
【解决方案2】:

这是一个使用 mv -n 技巧的 bash 函数:

function mkatomic() {
  f="$(mktemp)"
  mv -n "$f" "$1"
  if [ -e "$f" ]; then
    rm "$f"
    echo "ERROR: file exists:" "$1" >&2
    return 1
  fi
}

例子:

$ mkatomic foo
$ wc -c foo
0 foo
$ mkatomic foo
ERROR: file exists: foo

【讨论】:

  • 至少在 GNU coreutils 8.28 mv -n 本身不是原子的,但在 8.32 中似乎还可以(或者至少更安全)。也许this change 修复了它。您可以使用while :; do rm -f c; echo a > a; echo b > b; mv b c & mv -n a c & wait; [ "$(< c)" = b ] || break; done; echo "mv -n is not atomic" 测试您的mv。如果这终止了,那么您的 mv -n 就不是原子的。如果它继续运行,它仍然可能是非原子的;但它运行的时间越长,mv -n 确实是原子的可能性就越大。
【解决方案3】:

您可以使用随机生成的名称创建它,然后将其重命名 (mv -n random desired) 以使用所需的名称。如果文件已经存在,重命名将失败。

像这样:

#!/bin/bash

touch randomFileName
mv -n randomFileName lockFile

if [ -e randomFileName ] ; then
    echo "Failed to acquired lock"
else
    echo "Acquired lock"
fi

【讨论】:

  • 我的“mv”不是原子的。它首先使用“stat”来测试目标文件是否存在,如果不存在则使用“rename”。但是“重命名”将简单地覆盖目标(如果它存在)。所以有一个小的时间范围(在“stat”和“rename”调用之间),其中使用目标名称创建的文件将被覆盖。
  • 啊,有趣。 mv 一点帮助都没有,那么抱歉。
【解决方案4】:

需要明确的是,确保文件仅在文件不存在时才被创建与原子性不同。当且仅当当且仅当当两个或多个单独的线程尝试同时执行相同的事情时,操作是原子的,只有一个会成功,而其他所有的都会失败。

我所知道的在 shell 脚本中以原子方式创建文件的最佳方式遵循这种模式(但并不完美):

  1. 创建一个极有可能不存在的文件(使用适当的随机数选择或文件名中的某些内容),并在其中放置一些独特的内容(其他线程没有的内容 - 再次,随机号码什么的)
  2. 验证文件是否存在并包含您期望的内容
  3. 创建从该文件到所需文件的硬链接
  4. 验证所需文件是否包含预期内容

特别是,touch 不是原子的,因为如果文件不存在,它将创建文件,或者只是更新时间戳。您也许可以玩不同时间戳的游戏,但读取和解析时间戳以查看您是否“赢得”比赛比上述更难。 mkdir 可以是原子的,但是您必须检查返回码,否则,您只能说“是的,目录已创建,但我不知道哪个线程获胜”。如果您使用的文件系统不支持硬链接,您可能不得不接受一个不太理想的解决方案。

【讨论】:

  • open 系统调用支持标志 O_EXCL,当与 O_CREAT 结合使用时,如果文件已存在,将导致 open 调用失败。这是一个原子操作。
  • @RyanPatterson 正确。但是您不能直接从bash 脚本中调用open()O_EXCL,这就是最初的问题所在。
  • { > file; } 构造保证了原子性,至少在符合 POSIX 的 shell 中是这样。您可以将 bash 切换到 POSIX 兼容模式,但即使没有,bash 在重定向原子性方面也不会偏离 POSIX。
【解决方案5】:

另一种方法是使用umask 尝试创建文件并打开它以进行写入,而不使用写入权限创建它,如下所示:

LOCK_FILE=only_one_at_a_time_please
UMASK=$(umask)
umask 777
echo "$$" > "$LOCK_FILE"
umask "$UMASK"
trap "rm '$LOCK_FILE'" EXIT

如果文件丢失,脚本将成功创建并打开它以进行写入,尽管该文件是在没有写入权限的情况下创建的。如果它已经存在,脚本将无法打开文件进行写入。可以使用exec 打开文件并保留文件描述符。

rm 要求您对目录本身具有写权限,而不考虑文件权限。

【讨论】:

    【解决方案6】:

    touch 是您要查找的命令。如果文件存在则更新所提供文件的时间戳,如果不存在则创建它。

    【讨论】:

    • 除非文件已经存在时不会失败,就像 O_EXCL 一样。
    • 我没有看过触摸源,但它可能不是原子的。哦,如果文件存在,它会同时改变访问和修改时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-10
    • 2020-08-18
    • 2018-10-09
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    相关资源
    最近更新 更多