在系统调用open()
中,如果使用O_CREAT | O_EXCL
打开,系统调用将确保仅在文件不存在的情况下创建该文件。原子性是由系统调用保证的。有没有类似的方法可以从bash脚本以原子方式创建文件?
更新:我发现了两种不同的原子方式
- 使用set-o nolobber。然后可以原子地使用>运算符
- 只需使用mkdir。Mkdir是原子的
100%纯bash解决方案:
set -o noclobber
{ > file ; } &> /dev/null
如果不存在名为file
的文件,则此命令将创建名为file
的文件。如果有一个名为file
的文件,则不执行任何操作(但返回一个非零的返回代码)。
>
优于touch
命令的优点:
- 如果文件已经存在,则不更新时间戳
- 100%bash内置
- 按预期返回代码:如果
file
已经存在或无法创建file
,则失败;如果file
不存在并且已创建,则成功
缺点:
- 需要设置
noclobber
选项(但在脚本中,如果您小心重定向或稍后取消设置,这是可以的)
我想这个解决方案实际上是open
系统调用O_CREAT | O_EXCL
的bash对应方案。
下面是一个使用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
您可以用随机生成的名称创建它,然后用所需名称重命名(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
需要明确的是,确保文件只在不存在的情况下创建与原子性不同。当且仅当两个或多个单独的线程试图同时做同一件事时,其中一个线程会成功,而其他所有线程都会失败时,该操作才是原子操作。
据我所知,在shell脚本中以原子方式创建文件的最佳方式如下(而且并不完美):
- 创建一个极有可能不存在的文件(使用适当的随机数选择或文件名中的某些内容),并在其中放置一些独特的内容(其他线程都不会拥有的内容,再次使用随机数或其他内容)
- 验证该文件是否存在并包含您期望的内容
- 创建从该文件到所需文件的硬链接
- 验证所需文件是否包含所需内容
特别是,touch
不是原子的,因为如果文件不在,它会创建文件,或者只是更新时间戳。你可能可以用不同的时间戳玩游戏,但读取和解析时间戳以查看你是否"赢得"了比赛比上面的更难。mkdir
可以是原子的,但您必须检查返回代码,否则,您只能告诉"是的,目录已经创建,但我不知道哪个线程会创建"。如果你所在的文件系统不支持硬链接,你可能不得不接受一个不太理想的解决方案。
另一种方法是使用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
要求您拥有对目录本身的写入权限,而不考虑文件权限。
touch
是您要查找的命令。如果提供的文件存在,它会更新该文件的时间戳,如果不存在,则会创建该文件。