原子地移动目录

时间:2008-11-21 00:48:59

标签: linux bash atomic

我在同一个父目录中有两个目录。调用父目录 base 和子目录 alpha bravo 。我想将 alpha 替换为 bravo 。最简单的方法是:

rm -rf alpha
mv bravo alpha

mv命令是原子的,但是rm -rf不是。在bash中有一种简单的方法可以用 bravo 原子地替换 alpha 吗?如果没有,是否有复杂的方法?

附录:

如果目录在短时间内不存在,那么这不是一个不可逾越的问题。只有一个地方试图访问alpha,它会在做任何关键事情之前检查alpha是否存在。如果没有,它会给出错误消息。但如果有办法做到这一点会很好。 :)也许有一些方法可以直接修改inode,或者其他东西...

16 个答案:

答案 0 :(得分:43)

最终的解决方案是结合符号链接和重命名方法:

mkdir alpha_real
ln -s alpha_real alpha

# now use "alpha"

mkdir beta_real
ln -s beta_real tmp 

# atomically rename "tmp" to "alpha"
# use -T to actually replace "alpha" instead of moving *into* "alpha"
mv -T tmp alpha

当然,访问alpha的应用程序必须能够处理路径中更改的符号链接。

答案 1 :(得分:14)

如果使用符号链接,则可以执行此操作:

假设alpha是目录alpha_1的符号链接,并且您希望将符号链接切换为指向alpha_2。这是切换之前的样子:

$ ls -l
lrwxrwxrwx alpha -> alpha_1
drwxr-xr-x alpha_1
drwxr-xr-x alpha_2

要使alpha引用alpha_2,请使用ln -nsf:

$ ln -nsf alpha_2 alpha
$ ls -l
lrwxrwxrwx alpha -> alpha_2
drwxr-xr-x alpha_1
drwxr-xr-x alpha_2

现在您可以删除旧目录:

$ rm -rf alpha_1

请注意,这实际上并不是完全原子操作,但它确实很快发生,因为“ln”命令都取消链接,然后立即重新创建符号链接。您可以使用strace验证此行为:

$ strace ln -nsf alpha_2 alpha
...
symlink("alpha_2", "alpha")             = -1 EEXIST (File exists)
unlink("alpha")                         = 0
symlink("alpha_2", "alpha")             = 0
...

您可以根据需要重复此过程:例如当你有一个新版本时,alpha_3:

$ ln -nsf alpha_3 alpha
$ rm -rf alpha_2

答案 2 :(得分:12)

在这里挑选David的解决方案,这是完全原子的...你遇到的唯一问题是-T的{​​{1}}选项是非POSIX,所以某些POSIX操作系统可能不支持它(FreeBSD,Solaris等...... http://pubs.opengroup.org/onlinepubs/9699919799/utilities/mv.html)。稍作修改,这种方法可以改为完全原子化,并可在所有POSIX操作系统中移植:

mv

exaple via:http://axialcorps.wordpress.com/2013/07/03/atomically-replacing-files-and-directories/

答案 3 :(得分:6)

如果你的意思是两种操作都是原子的,我不相信。最接近的是:

mv alpha delta
mv bravo alpha
rm -rf delta

但是仍然会有一个不存在alpha的小窗口。

为了尽量减少任何尝试使用alpha的可能性,而不是那样你可以(如果你有权限):

nice --20 ( mv alpha delta ; mv bravo alpha )
rm -rf delta

会在mv操作发生时大幅提升您的流程优先级。

如果正如你在附录中所说的那样,只有一个地方可以检查alpha并且如果它不存在就会出现错误,你可以立即将该代码改为不错误,但在短时间内再试一次(很容易在亚秒内两个mv操作) - 除非您经常更换alpha 非常,否则这些重试应该可以缓解任何问题。

答案 4 :(得分:6)

使用单独的,保证原子的操作来充当信号量。

因此,如果创建和删除文件操作是原子操作:

1)创建一个名为“semaphore”的文件。

2)当且仅当它成功(与现有文件没有冲突)时,执行操作(处理alpha或移动目录,具体取决于进程)

3)rm信号量。

答案 5 :(得分:4)

SQLite文档部分File Locking and Concurrency in SQLite Version 3对其升级锁定协议有一个精心编写的描述,用于控制崩溃后的并发读取,独占写入和回滚。其中一些想法适用于此。

答案 6 :(得分:3)

这应该可以解决问题:

mkdir bravo_dir alpha_dir
ln -s bravo_dir bravo
ln -s alpha_dir alpha
mv -fT bravo alpha

strace mv -fT bravo alpha显示:

rename("bravo", "alpha")

对我来说看起来很原子。

答案 7 :(得分:3)

从Linux 3.15开始,新的renameat2系统调用可以在同一文件系统上原子地交换两个路径。但是,它还没有glibc包装器,更不用说coreutils方式来访问它了。所以它看起来像这样:

int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
syscall(SYS_renameat2, dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");

(当然,您应该进行适当的错误处理等 - 请参阅this gist了解更复杂的renameat2包装。)

那就是说 - 其他人提到的符号链接解决方案既简单又便携,所以除非bravo已经存在并且必须以原子方式更新它,否则请改为使用符号链接。

答案 8 :(得分:1)

即使您直接访问inode,仍然无法以原子方式交换用户空间中的inode值。

答案 9 :(得分:1)

担心操作的原子性质是没有意义的。问题是,其他任务对alpha的访问无论如何都不会是原子的。

Oddthinking的信号量方法是唯一的出路。

如果您无法修改其他任务,那么在进行更换之前,您必须确保它没有运行。

答案 10 :(得分:0)

我不相信有任何原子方法可以做到这一点。你最好的选择是做这样的事情:

mv alpha delme
mv bravo alpha
rm -rf delme

答案 11 :(得分:0)

要记住的一点是,如果您的进程在执行此移动/删除时打开alpha中的任何文件,则该进程将不会注意到,并且在文件关闭并最终删除时,所写的任何数据都将丢失。

答案 12 :(得分:0)

mv和ln可用于原子操作。我已经使用ln(1)以原子方式部署Web应用程序。

替换符号链接的正确方法是使用ln -nsf

ln -nsf <target> <link_name>

e.g。

$ mkdir dir1
$ mkdir dir2
$ ln -s dir1 mylink
$ ls -l mylink
lrwxrwxrwx  1 phil phil 4 Nov 16 14:45 mylink -> dir1
$ ln -nsf dir2 mylink
$ ls -l mylink
lrwxrwxrwx  1 phil phil 4 Nov 16 14:46 mylink -> dir2

答案 13 :(得分:0)

还可以使用Z在某个前缀(unionfs-fuse此处)中一次替换内容的整个部分:

# mkdir a b c Z
# touch a/1 b/2 c/3
# ln -s a X
# ln -s b Y
# unionfs X=RW:Y=RW Z
# shopt -s globstar
# file **
a:   directory
a/1: empty
b:   directory
b/2: empty
c:   directory
c/3: empty
X:   symbolic link to a
Y:   symbolic link to b
Z:   directory
Z/1: empty
Z/2: empty
# ln -sfn c Y
# file **/*
a:   directory
a/1: empty
b:   directory
b/2: empty
c:   directory
c/3: empty
X:   symbolic link to a
X/1: empty
Y:   symbolic link to c
Y/3: empty
Z:   directory
Z/1: empty
Z/3: empty
# fusermount -u Z
# rm -r a b c X Y Z

答案 14 :(得分:0)

mount --bind bravo alpha应该在Linux上执行

它保留了alpha的内容,但如果要清除它,则可以在其他位置绑定挂载父文件系统。

如果文件系统是NFS导出的,则可能需要确保允许导出选项跨越文件系统边界,并在服务器上进行绑定安装。

您还需要注意在alpha目录或子目录(例如cwd)上具有打开句柄的进程。

其他* nix可能有类似的窍门,但尚未标准化。

答案 15 :(得分:-2)

你为什么不这样做:

rm -rf alpha/*
mv bravo/* alpha/
rm -rf bravo/

这意味着 alpha中的所有都会被销毁,alpha 永远不会被删除,所有内容都会被移动。