交换文件而不是 cp 与临时文件?

Aar*_*kon 5 linux files

有一个著名的入门级编程练习,要求您交换两个变量。显而易见的解决方案是使用第三个临时变量。但是,如果您的语言有类似元组的东西,您可以编写一个非常简单的辅助函数,以相反的顺序返回其参数:

def swap(a: Int, b: Int): (Int, Int) = (b, a)
val (two, one) = swap(1, 2) // => (2, 1)
Run Code Online (Sandbox Code Playgroud)

我想知道Linux上的文件操作是否也可以这样。例如,如果我有一个要根据情况交换的配置,是否有一个命令可以获取两个文件名并交换它们的内容?
例如,假设文件 a.txt 的内容为“Hello”,而 b.txt 的内容为“World”。调用我在这里调用的内容后swap,我希望 a.txt 包含“World”和 b.txt“Hello”。

这个问题比我最初想象的更难研究,因为交换分区主导了所有搜索结果。

A.B*_*A.B 13

有一个特定于 Linux 的系统调用能够在内核级别执行此操作。

相关的系统调用是renameat2()Linux 特定的扩展renameat(),可以与附加的特定标志一起使用来解决这个问题。Linux 3.15 中添加了它,glibc 2.28 中添加了 glibc 支持。

RENAME_EXCHANGE

原子交换oldpathnewpath。两个路径名都必须存在,但可以是不同的类型(例如,一个可以是非空目录,另一个可以是符号链接)。

哪些文件系统可以支持此功能可能还有进一步的限制。这个git 搜索讲述了自 2014 年以来添加的各种文件系统的支持:ext4、fuse、f2fs、shmem/tmpfs、xfs、gfs2、overlayfs、btrfs、ubifs、affs ...

如果没有正确的命令来使用此系统调用,这里有一个 Python 中的示例,该示例非常特定于体系结构 (amd64/x86_64),其中所有符号均“手动”解析(在此体系结构中为= 316 等),以原子方式交换名为和的SYS_renameat2文件,显示 C 中的系统调用会执行什么操作:abstrace

$ echo Hello > a.txt; echo World > b.txt
$ cat a.txt
Hello
$ cat b.txt
World
$ strace -e trace=renameat2 python3 -c 'import ctypes; libc = ctypes.CDLL(None); libc.syscall(316, -100, b"a.txt", -100, b"b.txt", 2);'
renameat2(AT_FDCWD, "a.txt", AT_FDCWD, "b.txt", RENAME_EXCHANGE) = 0
+++ exited with 0 +++
$ cat a.txt
World
$ cat b.txt
Hello
Run Code Online (Sandbox Code Playgroud)

当然,使用适当的库可以简化这个 Python 示例。

  • 我有这样的感觉,如果我写下“它不存在”,很快就会有人过来指出它实际上是存在的。而且甚至只花了23分钟!:D (4认同)

ilk*_*chu 9

我不认为有这样一个标准的系统调用。不过 Linux 上有一个。

拥有或不拥有专用系统调用很重要,因为虽然您当然可以rename(a, tmp); rename(b, a); rename(tmp, b);在任何系统上执行此操作,但它不是原子的。同时运行的另一个进程可能会a在重命名后立即尝试打开,并且会收到“文件未找到”错误。即使您首先将其替换rename()link(),确保它a始终存在,另一个进程也可能能够打开ab并获取相同的文件。

幸运的是,并不经常需要交换两个文件(我也不确定交换两个变量是否是常见的需要)。相反,更常见的需求是替换单个文件名后面的内容,而不留下读者可能获得部分、混合或丢失数据的点。这可以通过rename(),link()或轻松完成symlink()

对于从一组配置更改为另一组配置的示例,您可以有一个指向活动配置的符号链接,然后自动替换该链接。

例如,如果你有

$ ls -l
total 8
-rw-r--r-- 1 ilkkachu ilkkachu  6 Oct 17 21:07 conf1
-rw-r--r-- 1 ilkkachu ilkkachu 13 Oct 17 21:07 conf2
lrwxrwxrwx 1 ilkkachu ilkkachu  5 Oct 17 21:07 current -> conf1
Run Code Online (Sandbox Code Playgroud)

然后ln -sf conf2 current会自动将其更改为

$ ls -l
total 8
-rw-r--r-- 1 ilkkachu ilkkachu  6 Oct 17 21:07 conf1
-rw-r--r-- 1 ilkkachu ilkkachu 13 Oct 17 21:07 conf2
lrwxrwxrwx 1 ilkkachu ilkkachu  5 Oct 17 21:07 current -> conf2
Run Code Online (Sandbox Code Playgroud)

同样,您可以将conf1current作为指向同一文件的硬链接,并将链接替换为ln -f conf2 current.