交换文件名的最佳方法

交换文件名的最佳方法

我需要交换两个文件(filefile_1)的文件名。我正在使用以下代码。

mv file .phfile
mv file_1 file
mv .phfile file

这可行,但有很多错误,有时甚至会导致数据丢失。有一个更好的方法吗?

答案1

renameat2Linux 系统上的 syscall,带有该RENAME_EXCHANGE标志,应该做到这一点,是一个声称使用它的cli工具。

答案2

在传统 Unix 系统中没有低级方法来交换文件,因此您需要使用中间临时名称。为了稳健性,请确保临时名称不会被任何其他程序使用(因此使用mktemp),并且它与其中一个文件位于同一文件系统上(否则文件将被不必要地复制,而不仅仅是重命名)。

swap_files () {
  tmp_name=$(TMPDIR=$(dirname -- "$1") mktemp) &&
  mv -f -- "$1" "$tmp_name" &&
  mv -f -- "$2" "$1" &&
  mv -f -- "$tmp_name" "$2"
}
swap_files file file_1

请注意,如果发生错误,第一个文件可能仍使用其临时名称,而第二个文件可能已移动,也可能尚未移动。如果您需要在发生中断和崩溃时具有鲁棒性,那么具有两个临时名称的变体可能更容易恢复。

swap_files2 () {
  tmp_dir1=$(TMPDIR=$(dirname -- "$1") mktemp -d .swap_files.XXXXXXXXXXXX) &&
  tmp_dir2=$(TMPDIR=$(dirname -- "$2") mktemp -d .swap_files.XXXXXXXXXXXX) &&
  mv -f -- "$1" "$tmp_dir1/" &&
  mv -f -- "$2" "$tmp_dir2/" &&
  mv -f -- "$tmp_dir1/"* "$1" &&
  mv -f -- "$tmp_dir2/"* "$2" &&
  rmdir -- "$tmp_dir1" "$tmp_dir2"
}

如果重新启动时临时目录.swap_files.????????????仍然存在,则意味着文件交换因电源故障而中断。请注意,其中一个文件可能已移动到位,而另一个文件尚未移动到位,因此此处的代码无法处理所有情况,这取决于您想要哪种恢复。

现代 Linux 内核(自 3.15 起,2014 年 6 月发布)有一个交换文件的系统调用:renameat2(…, RENAME_EXCHANGE).然而,似乎没有一个通用的命令行实用程序。甚至 glibc 支持也是最近才添加的(2.28,于 2018 年 8 月发布)。

答案3

聚会有点晚了但是您可以在较新和较旧版本的 Linux 中自动名称交换文件通过使用tccor gcc(在所有主要 Linux 发行版中都可用)来准时生产你自己是一个低级工具,它使用正确的内核系统调用并使用它。无需第三方工具:

swapname() {
    tcc -run - "$@" <<"CODE"
    #include <unistd.h>
    #include <fcntl.h> 
    #include <stdio.h>
    #include <sys/syscall.h>
    
    // Ubuntu 18.04 does not define RENAME_EXCHANGE
    // Value obtained manually from '/usr/include/linux/fs.h'
    // You should switch to RENAME_EXCHANGE on modern systems
    // Just remove the following line, then remove the `local_`
    // prefix where it appears later in this function.
    int local_RENAME_EXCHANGE = (1 << 1);
    
    int main(int argc, char **argv) {
        if (argc != 3) { 
            fprintf(stderr, "Error: Could not swap names. Usage: %s PATH1 PATH2\n", argv[0]);
            return 2; 
        }
        int r = syscall(
            SYS_renameat2,
            AT_FDCWD, argv[1],
            AT_FDCWD, argv[2], 
            local_RENAME_EXCHANGE
        );
        if (r < 0) {
            perror("Error: Could not swap names");
            return 1;
        }
        else return 0;
    }
CODE
} 

按以下方式运行此 bash 函数将干净且原子地交换文件名:

swapname "/path/to/file-1" "/path/to/file-2"

请注意,renameat2withRENAME_EXCHANGE可能要求两个文件位于同一文件系统安装点下。请参阅手册页中的错误部分renameat2(即rename(2)) 了解更多信息。

使用 GCC 代替 TCC

如果您更喜欢使用gcc而不是tcc,只需删除以 开头的行tcc -run ...并在其位置添加以下行:

( EXEC="$(mktemp)" && gcc -x c - -o "$EXEC" && "$EXEC" "$@"; rm "$EXEC" ) <<"CODE" 

答案4

这是我最终使用的:

file1=1stfile
file2=2ndfile

tempdir="$(mktemp -d)"

mv "$file1" "$tempdir/tmpfile" &&
mv "$file2" "$file1" &&
mv "$tempdir/tmpfile" "$file2" &&
rm -rf "$tempdir"

相关内容