45

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

rm -rf alpha
mv bravo alpha

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

附录:

顺便说一句,如果目录在短时间内不存在,这不是一个无法克服的问题。只有一个地方会尝试访问 alpha,它会在执行任何关键操作之前检查 alpha 是否存在。如果不是,它会给出错误消息。但如果有办法做到这一点,那就太好了。:) 也许有一些方法可以直接修改 inode,或者什么......

4

16 回答 16

58

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

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 的应用程序必须能够处理路径中更改的符号链接。

于 2012-06-04T19:15:48.490 回答
21

如果您使用符号链接,您可以这样做:

假设 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
于 2008-11-21T01:28:28.877 回答
17

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

mkdir -p tmp/real_dir1 tmp/real_dir2
touch tmp/real_dir1/a tmp/real_dir2/a
# start with ./target_dir pointing to tmp/real_dir1
ln -s tmp/real_dir1 target_dir
# create a symlink named target_dir in tmp, pointing to real_dir2
ln -sf tmp/real_dir2 tmp/target_dir
# atomically mv it into ./ replacing ./target_dir
mv tmp/target_dir ./

例如通过:http ://axiscorps.wordpress.com/2013/07/03/atomically-replacing-files-and-directories/

于 2013-07-05T15:55:16.367 回答
13

从 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");

(当然,您应该进行适当的错误处理等 - 请参阅此要点以获得更复杂的renameat2包装器。)

也就是说 - 其他人提到的符号链接解决方案更容易和可移植,所以除非bravo已经存在并且您必须自动更新它,否则请改用符号链接。


2020 年更新:自 2018 年 8 月 1 日发布的 glibc 2.28(Debian Stretch,Fedora 29)以来,此系统调用的 glibc 包装器可用。不过,它仍然无法通过 coreutils 访问。

int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
renameat2(dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");
于 2018-05-15T15:05:25.273 回答
8

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

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

1)创建一个名为“信号量”的文件。

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

3)rm信号量。

于 2008-11-21T01:33:45.230 回答
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,否则这些重试应该可以缓解任何问题。

于 2008-11-21T00:51:57.017 回答
4

SQLite文档第3 版中的文件锁定和并发部分对其升级锁定协议进行了很好的描述,以控制并发读取、独占写入和崩溃后的回滚。其中一些想法适用于此。

于 2009-11-07T23:00:18.477 回答
4

这应该可以解决问题:

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")

这对我来说看起来很原子。

于 2014-01-06T21:50:52.983 回答
1

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

于 2008-11-21T01:19:56.977 回答
1

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

Oddthinking 的信号量方法是唯一可行的方法。

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

于 2008-11-21T04:17:12.390 回答
0

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

mv alpha delme
mv bravo alpha
rm -rf delme
于 2008-11-21T00:51:34.287 回答
0

需要记住的是,如果您的进程在此移动/删除发生时打开了任何处于 alpha 阶段的文件,则该进程不会注意到,并且当文件关闭并最终删除时,写入的任何数据都将丢失。

于 2008-11-21T03:38:51.237 回答
0

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

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

ln -nsf <target> <link_name>

例如

$ 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
于 2009-11-16T14:48:00.087 回答
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
于 2018-01-12T20:09:16.507 回答
0

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

它会隐藏 alpha 的内容,但如果您想清除它,您可以将父文件系统绑定到其他位置。

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

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

其他 *nix 可能有类似的技巧,但它不是标准化的。

于 2019-11-01T15:24:07.210 回答
-2

你为什么不做类似的事情:

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

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

于 2008-11-21T04:44:07.250 回答