Best way to swap filenames

There's no low-level way to swap files in traditional Unix systems, so you need to use an intermediate temporary name. For robustness, make sure that the temporary name won't be used by any other program (so use mktemp) and that it's on the same filesystem as one of the files (otherwise the files would be needlessly copied instead of being just renamed).

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

Beware that if an error occurs, the first file could still be under its temporary name, and the second file may or may not have been moved yet. If you need robustness in case of interruptions and crashes, a variant with two temporary names may be easier to recover from.

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

If the temporary directories .swap_files.???????????? are present on a reboot, it means that a file swap was interrupted by a power failure. Beware that it's possible that one of the files has already been moved into place and the other one hasn't, so the code here doesn't take care of all cases, it depends what kind of recovery you want.

Modern Linux kernels (since 3.15, relased in June 2014) have a system call to swap files: renameat2(…, RENAME_EXCHANGE). However there doesn't seem to be a commonly available command line utility for it. Even glibc support was only added recently (2.28, released in August 2018).


The renameat2 syscall on Linux systems, with the RENAME_EXCHANGE flag, should do exactly that, this is a cli tool that claims to use it.


Here's what I ended up using :

file1=1stfile
file2=2ndfile
temp="$(mktemp -dp /mnt/sdcard)"
mv "$file1" $temp
mv "$file2" "$file1"
mv $temp/"$file1" "$file2"