Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

mv can't move a directory to a destination with a same-name directory:

$ mv fortran/ imperative_PLs/
mv: cannot move ‘fortran/’ to ‘imperative_PLs/fortran’: Directory not empty
  1. Why does mv not work in this case? Can that be explained from the system calls mv invokes? (Compare to rsync which can)

  2. Why is mv designed to not work in this case? What is the rationale or point?

share|improve this question
up vote 5 down vote accepted
  1. mv doesn't work in this case because it's not been designed to do so. The system calls are (probably) either

    • Move to same filesystem: rename (originally link and unlink)
    • Move across filesystems: recursive file copy followed by recursive unlink
  2. Opinion: I think it's not so much that it was designed not to work, as it wasn't designed to handle this use case. For a "simple" tool that's intended to do one thing well you'd need to provide a set of switches to indicate to mv which of these action paths to take:

    • To bail with an error, as in the current implementation
    • To merge, bailing with an error if a file already exists
    • To merge, replacing any target files that already exist

If the merge/replace action is what you want, you can implement it easily enough with cp followed by rm, or by using one of the file tree copying utilities tar, pax, etc.

share|improve this answer
    
Thanks. (1) Does rm call rmdir() besides rename()? (2) "Move to same filesystem: rename (originally link and unlink)", what do you mean by "originally link and unlink"? (3) When Move across filesystems, why "recursive file copy followed by recursive unlink"? – Tim Mar 26 at 1:26
    
my typo. I meant mv instead of rm. – Tim Mar 26 at 6:04
    
@Tim: Within the same filesystem, mv uses only rename(). Between filesystems, mv does essentially the same as cp -r (but beware handling of special files like symlinks, devices, etc. which are, traditionally, not handled well by cp -r) followed by rm -r, so yes, it uses rmdir() in that mode. – Guntram Blohm Mar 26 at 6:11

mv and rsync are not similar programs. In particular, mv is often attempting to simply rename objects. If it's in the same filesystem, it does not copy the contents at all.

If you didn't already have imperative_PLs/fortran, then mv would take the existing fortran directory and rename it to that point in the tree.

But you already have a directory (with contents) at that location. Because a name can only reference a single object, the existing directory would have to be either removed or renamed. mv assumes you don't want to do either and aborts.

rsync instead copies the individual files and other contents inside fortran and puts them into the existing imperative_PLs/fortran directory.

Think of it as rename instead, and the behavior might seem more understandable.

share|improve this answer

mv is actually rename under the cover.

If you move a file to another file, mv assumes you know what you are doing and overwrite the destination file.

If you move a directory to another directory, mv assumes you want to keep the basename of your original directory and create it on the target directory. If there is not yet a directory with that name on the destination side, or if a directory with that name exists but is empty, the operation succeeds.

However, if the target directory already exists and is not empty, this is no more a rename but that should be a recursive file and directory removal. rename isn't designed to do it so it fails, mv doesn't go further as it assumes you didn't want to do it and fails too.

share|improve this answer
    
Thanks. Does rm call rmdir() besides rename()? – Tim Mar 26 at 1:14
1  
@Tim: rm doesn't ever call rename(). But it does call rmdir() when given the -r option. – Guntram Blohm Mar 26 at 5:20
    
@Gun: My typo. I meant mv instead of rm. – Tim Mar 26 at 6:04
1  
mv calls rmdir() only if the source and destination file systems are differents. One reason is it is not anymore the same inode that has a new path. – jlliagre Mar 26 at 6:52

The error message when moving between filesystems is slightly more verbose:

# mv a/foo b/bar
mv: inter-device move failed: 'a/foo' to 'b/bar/foo'; unable to remove target: Directory not empty

So it's not trying to merge directories like you seem to expect, instead it's removing the target before renaming the source; and removing for directories only works when it's empty.

In terms of syscalls, within the same filesystem, it's just rename()

rename("a/foo", "a/bar/foo")            = -1 ENOTEMPTY (Directory not empty)

When moving between filesystems, it's first rename() detecting this case and them a simple attempt at rmdir().

rename("a/foo", "b/bar/foo")            = -1 EXDEV (Invalid cross-device link)
rmdir("b/bar/foo")                      = -1 ENOTEMPTY (Directory not empty)

mv could make more of an effort but it doesn't want to. ;)

share|improve this answer
    
thanks. does mv call rmdir()? I tried one with strace -e trace=file on mv, rmdir() doesn't come up. – Tim Mar 26 at 1:06
    
only when crossing filesystem boundaries, otherwise I guess it's rename() handling it internally – frostschutz Mar 26 at 3:22

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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