在Bash中避免文件创建和修改过程中的竞态条件
假设有一个任务,需要在目标路径不存在时才创建并写入一个文件(这意味着如果目标路径已经是一个目录、符号链接,或其他任何东西,任务就会失败)。在bash中实现最简单的方式是这样的:
if [[ -e "$file_path" || -L "$file_path" ]]
then
# Either throw an error, exit the script,
# or something like that here.
fi
output_the_data > "$file_path"
这里的问题是,在测试和 output_the_Data > "$file_path" 之间,另一进程可能会在路径 $file_path 上创建某些东西。我已经知道,本地程序,或用能够与本地库接口的语言编写的脚本(例如Python),很可能可以依赖Linux的文件IO相关系统调用的特性——或者如果在Windows上运行Bash,就依赖Windows的系统调用——来避免这里的竞态条件。举例来说,可能通过某种原子性检查后再创建模式来完成,或者它打开一个文件描述符,在该描述符上进行测试(例如,用来判断在打开描述符之前路径是否存在)。在后一种情况下,如果在正在被创建它的进程使用时该文件被删除,那么就不会产生错误,因为它的名字只是从那个路径上被取消链接(unlink)。
我考虑过检查 tee 是否提供某种“路径已存在时失败”的模式;它没有。因此,将这个问题限定在仅在使用一个 bash 环境的情形下,如何避免上述竞态条件?是否也存在一种方法,可以打开一个文件描述符并测试它来查看在打开前它是否存在且是一个普通文件?或者是否有一种原子地只有在路径不存在时才打开文件描述符的方法?
解决方案
使用 noclobber 标志,通过 set -C 启用(或通过以 bash -C 运行你的脚本来启用):
set -C
output_the_data > "$file_path" || exit
如果 file_path 已经存在,shell将输出类似于 yourscript: line 2: file_path: cannot overwrite existing file 的错误,并在 output_the_data 启动之前退出。
如果你想在noclobber触发时提前退出 *、且仅在 output_the_data 以任何其他方式失败时不触发,那可能是这样的:
set -C # enable noclobber
exec 3>"$file_path" || exit # open output file
set +C # disable noclobber for rest of the script
output_the_data >&3 # actually run the subprocess with output to file
exec 3>&- # close output file