How to swap filenames in Unix?

2019-05-03 13:43发布

问题:

Any way to do this quickly without using a temp variable? Is there a built in function?

Edit: Thanks for the answers guys. Looks like I need to clarify my question but for the most part you guys assumed correctly: There are two files and the filenames names are reversed.

  • File A has name B-name.file
  • File B has name A-name.file

I'd like for File A to be named A-name.file and File B to be named B-name.file.

I agree, the situation doesn't happen often but it just happened to me and I wanted a quick fix.

回答1:

ok, stupid question, but why can't you simply do something like (in a shell script):

mv $fileA $fileA.$$
mv $fileB $fileA
mv $fileA.$$ $fileB

and yes of course it uses a temporary file, but its more concise then the other answers.



回答2:

Darwin/Mac OS X has the exchangedata() system call:

The exchangedata() function swaps the contents of the files referenced by path1 and path2 in an atomic fashion. That is, all concurrent processes will either see the pre-exchanged state or the post-exchanged state; they can never see the files in an inconsistent state.

However it only actually works on a few filesystems that specifically support it (such as Apple's HFS and HFS+), and I haven't seen any similar system call on other systems. The portable way to do this is using a third temporary file name, and the operation will not be atomic.



回答3:

This can be done with little helper, just put in .bashrc, .zshrc or where your configs.

function swap() { mv $1 $1._tmp && mv $2 $1 && mv $1._tmp $2; }

And use it as regular function:

$ cat a b
Alfa
Beta

$ swap a b && cat a b
Beta
Alfa


回答4:

At shell script level - there isn't a standard command and the rename involves at least a temporary file name (beware files on different file systems!).

At the C code level, there isn't a standard function available on all machines to swap file names - and one of the factors is the issue of dealing with files on different file systems.

On a single file system:

file1=one-file-name
file2=tother-file
file3=tmp.$$

trap "" 1 2 3 13 15
ln $file1 $file3
rm $file1
ln $file2 $file1
rm $file2
ln $file3 $file2
rm $file3
trap 1 2 3 13 15

That isn't completely fool-proof - but it is a semi-decent approximation if $file1 and $file2 are on the same file system (and I've assumed $file3 is a name on the same file system). Fixing it to deal with all the warts is ... non-trivial. (Consider $file1 == $file2, for instance.)

The code simulates pretty much the system calls that a C program would have to make - ln to map to link() and rm to map to unlink(). The newer (as in, only twenty years old) function rename() can probably be used to good effect - provided you understand what it won't do. Using the mv command instead of link and remove means that the files will be moved between file systems if needed; that might be helpful - or it might mean that your disk space fills up when you didn't intend it to. Recovering from an error part way through is not entirely trivial, either.



回答5:

Perhaps it could be done with:

file_B = fopen(path_b, 'r');
rename(path_a, path_b);

file_B_renamed = fopen(path_a, 'w');
/* Copy contents of file_B into file_B_renamed */
fclose(file_B_renamed);
fclose(file_B);

There could be a way (I'm searching through the POSIX spec to see, but I wouldn't bet on it) to create a hardlink from the inode number; which would end up doing something like

file_B = fopen(path_b, 'r');
rename(path_a, path_b);
make_hardlink(file_B, path_a);


回答6:

What does "swap filenames" mean? Are you talking about the file system, or just variables in your program?

If your program is C++, and you have two filenames in strings, and want to swap them, use std::swap. This only changes the variables in the program:

std::string filenameA("somefilename");
std::string filenameB("othername");

std::swap(filenameA, filenameB);
std::cout << filenameA << std::endl; // prints "othername"

If you have two files on the disk, and you want the names to swap content with each other, then no, there's no way to easily to that, if you want to preserve hard links. If you just want to perform a "safe save," then the unix rename() system call will clobber the destination file with the source file in an atomic operation (as atomic as can be supported by the underlying filesystem). Thus, you'd safe-save like this:

std::string savename(filename);
savename += ".tmp";
... write data to savename ...
if (::rename(savename.c_str(), filename.c_str()) < 0) {
  throw std::exception("oops!");
}

If you really, truly need to swap the files on disk (say, to keep a backup copy), then enter hard-linking. Note: hard-linking isn't supported on all file systems (specifically some SMB file shares), so you'll need to implement some backup if needed. You will need a temporary file name, but not any temporary data store. While you can implement it manually with link() and unlink() (remember: files can have multiple hard links in UNIX), it's easier just using rename(). You can't do a swap entirely atomically, though.

assuming oldfile is "filname.dat" and newfile is "filename.dat.bak" you'll get this:

::link(oldfile, tempfile);
::rename(newfile, oldfile);
::rename(tempfile, newfile);

If you crash after the link, you'll have the new data in newfile, and the old data in oldfile and tempfile. If you crash after the first rename, you'll have the new data in oldfile, and the old data in tempfile (but not newfile!)