我需要交换两个文件(file
和file_1
)的文件名。我正在使用以下代码。
mv file .phfile
mv file_1 file
mv .phfile file
这可行,但有很多错误,有时甚至会导致数据丢失。有一个更好的方法吗?
答案1
答案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 中自动名称交换文件通过使用tcc
or 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"
请注意,renameat2
withRENAME_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"