在系统调用open()
中,如果我使用O_CREAT | O_EXCL
打开,系统调用将确保仅在文件不存在时才会创建该文件。系统调用保证了原子性。是否有类似的方法从bash脚本以原子方式创建文件?
更新: 我发现了两种不同的原子方式
答案 0 :(得分:21)
100%纯bash溶液:
set -o noclobber
{ > file ; } &> /dev/null
如果没有名为file
的现有文件,此命令将创建名为file
的文件。如果有一个名为file
的文件,则不执行任何操作(但返回非零返回码)。
专业人员touch
命令:
file
已存在或无法创建file
,则会失败;如果file
不存在且已创建,则成功。缺点:
noclobber
选项(但在脚本中可以正常使用,如果您对重定向很小心,或者之后取消设置)。我猜这个解决方案实际上是与open
进行O_CREAT | O_EXCL
系统调用的bash对应。
答案 1 :(得分:5)
这是使用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
答案 2 :(得分:2)
为了清楚起见,确保仅在不存在的情况下创建文件与原子性不同。当且仅当两个或多个单独的线程同时尝试执行相同的操作时,该操作才是原子操作,只有一个将成功,而其他所有线程都将失败。
我知道在shell脚本中以原子方式创建文件的最佳方式遵循此模式(并且它并不完美):
特别是,touch
不是原子的,因为如果文件不存在,它将创建文件,或者只是更新时间戳。您可能能够玩具有不同时间戳的游戏,但是阅读和解析时间戳以查看您是否“赢得”比赛比上述更难。 mkdir
可以是原子的,但是您必须检查返回代码,否则,您只能告诉“是的,目录已创建,但我不知道哪个线程赢了”。如果您使用的文件系统不支持硬链接,则可能需要采用不太理想的解决方案。
答案 3 :(得分:2)
您可以在随机生成的名称下创建它,然后使用所需名称将其重命名(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
答案 4 :(得分:0)
执行此操作的另一种方法是使用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
要求您具有目录本身的写权限,而不考虑文件权限。
答案 5 :(得分:-3)
touch
是您要查找的命令。如果文件存在,它会更新所提供文件的时间戳,如果不存在则会创建它。