0

Problem: I'm looking for a way to rename or copy a file without overwriting the destination file, if it exists, and then check the success of the move or copy operation. I'm seeking a method that will work with the BSD versions of mv/cp installed on MacOS/Unix, and also the GNU coreutils versions I have on Linux.

Solution attempt: In all versions of mv/cp, I can prevent overwriting the destination file with the -n flag:

mv -n file1 file2
cp -n file1 file2 

Similar questions suggest testing the success of mv and cp using the exit status, which is 0 if successful and >0 if an error occurred. However, for both versions of mv/cp, the exit code is 0 when the destination file already exists and the -n flag is used.

The only other option I can think of is to also use the -v flag, and look at the output of the command:

mv -nv file1 file2
cp -nv file1 file2

However, the GNU and BSD versions of mv/cp behave differently when the -nv flags are used and file2 already exists: the GNU versions of mv/cp return nothing, whereas the BSD versions return file2 not overwritten.

Our previous method was to check whether the destination file exists first, then do the mv/cp operation. Believe it or not, this caused problems because the destination file would sometimes get created by another process between the time that the check was performed and the mv/cp operation was executed.

Is there a way to accomplish this task that works with both BSD and GNU versions of mv/cp?

Alternatively, is there are way to do this using Python 2? I couldn't find a way to do this using os.rename()

1

If you have bash - https://stackoverflow.com/questions/13828544/atomic-create-file-if-not-exists-from-bash-script

set -o noclobber
{ > file ; } &> /dev/null

This command creates a file named file if there's no existent file named file. If there's a file named file, then do nothing (but return a non-zero return code).

I.e. create an empty file first using this technique. If that succeeds, you can then overwrite the empty file.

Similarly for python. Use os.open() to create an empty file, making sure to include O_EXCL in the flags. ("For a description of the flag and mode values, see the C run-time documentation." See POSIX standard / Linux man page).


The bash technique is using O_EXCL behind the scenes. There is also RENAME_NOREPLACE, but it is a relatively recent addition in Linux, and I do not think it is present on OS X.

  • 1
    I guess you meant do something like os.open("file", os.O_CREAT|os.O_EXCL) ? – srcerer Jan 25 at 20:20
  • Sorry, I can't upvote your answer due to my noobness. If you upvote my question I might pass the 15 reputation required to upvote your answer! – srcerer Jan 25 at 20:27
  • Hi @srcerer, no sweat. Marking the answer as the accepted one (if that's what you want to do) gives more rep anyway. – sourcejedi Jan 25 at 21:05
  • I have edited and linked to reference documentation for os.open(). I am being lazy here and just pointing you towards O_EXCL, didn't feel like checking through all the implications (in two different languages). If you want to accept (and/or write) an answer that provides more detail, that's fine. – sourcejedi Jan 25 at 21:17
  • I don't want to mark it as accepted because it's such a lame question (as indicated by the 0 upvotes). Who cares if someone with 1 rep accepts your answer anyhow, am I right? – srcerer Jan 25 at 21:18
0

FreeBSD does indeed return failure if cp -n is asked to overwrite a file:

$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # this should fail
$ cp -n foo.1 foo.2 || echo fail
fail
$ rm foo.2
$ # this should succeed
$ cp -n foo.1 foo.2 || echo fail
$ exit

You are correct when you state that FreeBSD's mv returns success even when the destination exists:

$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # this should fail
$ mv -n foo.1 foo.2 || echo fail
$ exit

One workaround is to && the mv result code with [ ! -f src-file ], as in:

$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # this should fail
$ ( mv -n foo.1 foo.2 && [ ! -f foo.1 ] ) || echo fail
fail
$ rm foo.2
$ # this should succeed
$ ( mv -n foo.1 foo.2 && [ ! -f foo.1 ] ) || echo fail
$ exit

Under GNU, neither utility performs the way you would like. The same workaround for mv works for me on Ubuntu, so that leaves GNU cp as as the one remaining problem case.

Just as an aside, I hear your comment about the race condition with testing for the destination file before calling cp, but it strikes me that the same race condition would be present even if cp did the right thing. The window of opportunity might be smaller, but my intuition is that it would still be there. IANAE, however.

Since the workaround for mv works on both platforms, perhaps this workaround will suffice:

$ rm -f foo.*
$ date > foo.1
$ date > foo.2
$ # this should fail
$ ( cp -n foo.1 TEMPFILE && mv -n TEMPFILE foo.2 && [ ! -f TEMPFILE ] ) || echo fail
fail
$ rm -f TEMPFILE
$ rm foo.2
$ # this should succeed
$ ( cp -n foo.1 TEMPFILE && mv -n TEMPFILE foo.2 && [ ! -f TEMPFILE ] ) || echo fail
$ rm -f TEMPFILE
$ exit

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.