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

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

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

def swap(a: Int, b: Int): (Int, Int) = (b, a)
val (two, one) = swap(1, 2) // => (2, 1)

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

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

答案1

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

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

RENAME_EXCHANGE

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

哪些文件系统可以支持此功能可能还有进一步的限制。这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

当然使用一个适当的图书馆简化这个Python例子。

答案2

我不认为有这样的标准系统调用。不过 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

然后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

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

答案3

看来您想避免文件内容的副本我可以想到两种方法来通过文件实现此目的。

  1. 用于mv通过临时文件重命名文件(不复制内容)。

  2. 使用符号链接(类似于指针)并显式设置您需要的配置而不是交换。例如

    
    $ls
    a.txt b.txt Define_setcfg.bash
    $猫a.txt
    你好
    $猫b.txt
    世界
    $猫define_setcfg.bash
    函数 setcfg() {
    echo 将配置设置为 $1 # 应检查 $1 是否设置为有效的 cfg 文件名
    ln -sf $1actual.cfg # 创建名为actual.cfg的$1符号链接
    }   
    
    $ source ./define_setcfg.bash # 定义当前bash中的函数
    $ setcfg b.txt # 使用函数setcfg
    将配置设置为 b.txt
    $ ls -l # 检查我们现在有什么
    总计 12
    lrwxrwxrwx 1 用户名 用户名 5 10 月 17 日 18:33 实际.cfg -> b.txt
    -rw-rw-r-- 1 用户名 用户名 6 Oct 17 18:08 a.txt
    -rw-rw-r-- 1 用户名 用户名 6 Oct 17 18:08 b.txt
    -rw-rw-r-- 1 用户名 用户名 161 十月 17 18:25 Define_setcfg.bash
    $ setcfg a.txt # 使用提供a.txt作为参数1的函数
    将配置设置为a.txt
    $ ls -l # 检查我们现在有什么
    总计 12
    lrwxrwxrwx 1 用户名 用户名 5 十月 17 18:34 实际.cfg -> a.txt
    -rw-rw-r-- 1 用户名 用户名 6 Oct 17 18:08 a.txt
    -rw-rw-r-- 1 用户名 用户名 6 Oct 17 18:08 b.txt
    -rw-rw-r-- 1 用户名 用户名 161 十月 17 18:25 Define_setcfg.bash
    $   

答案4

这样的命令很容易编写,所以我怀疑有一个数字存在。但为什么?

你真正需要解决的问题是什么?在大多数情况下,有更好的方法。

如果您有一个程序读取具有特定名称的文件,并且您想要两个版本,那么我通常所做的就是在文件系统中拥有两个版本,其名称表明其可用性,并使用指向其中一个版本的符号链接,其名称为该程序的名称。实际使用。然后我只需更新符号链接(使用ln -sf)即可在它们之间进行更改。

相关内容