12

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?

5 Answers 5

10

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.

0
7
  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.

3
  • 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, 2016 at 1:26
  • my typo. I meant mv instead of rm.
    – Tim
    Mar 26, 2016 at 6:04
  • 1
    @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. Mar 26, 2016 at 6:11
7

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.

4
  • Thanks. Does rm call rmdir() besides rename()?
    – Tim
    Mar 26, 2016 at 1:14
  • 1
    @Tim: rm doesn't ever call rename(). But it does call rmdir() when given the -r option. Mar 26, 2016 at 5:20
  • @Gun: My typo. I meant mv instead of rm.
    – Tim
    Mar 26, 2016 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, 2016 at 6:52
3

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. ;)

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

If you want to replace the content inside of a dir with another, you can use

mv --backup=simple src dest

Which will rename the dest to dest~ and move the src to dest.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .