bashshellcommand-linemv

Replace a folder with another folder using mv (without deleting target folder first)


I am trying to get a directory to replace an existing folder but can't get it done with mv - I believe there's a way and I just don't know it (yet). Even after consulting the man page and searching the web.

If /path/to/ only contains directory, the following command will move /path/to/directory (vanishes) to /path/to/folder

mv /path/to/directory /path/to/folder

It is basically a rename, which is what I try to achieve.

But if /path/to/folder already exists, the same command moves the /path/to/directory to /path/to/folder/directory.

I do not want to use cp command to avoid IO.


Solution

  • Instead of using cp to actually copy the data in each file, use ln to make "copies" of the pointers to the file.

    ln /path/to/directory/* /path/to/folder && rm -rf /path/to/directory
    

    Note this is slightly more atomic than using cp; each individual file appears in /path/to/folder in a single step (i.e., there is no chance that /path/to/folder/foo.txt is ever partially copied), but there is still a small window where some, but not all, files from /path/to/directory have been linked to folder. Also, the rm -rf is not atomic, but assuming no one is interested in directory, that's not an issue. (Although, as files from /path/to/directory are unlinked, you can see changes to the link counts of files under /path/to/foldoer changing from 2 to 1. It's unlikely that anyone will care about that.)


    What you think of as a file is really just a file system entry to an otherwise anonymous file managed by the file system. For example, consider a simple example.

    $ mkdir d
    $ cd d
    $ echo hello > file.txt
    $ cp file.txt file_copy.txt
    $ ln file.txt file_link.txt
    $ ls -li
    total 24
    12890456377 -rw-r--r--  2 chepner  staff  6 Mar  3 12:46 file.txt
    12890456378 -rw-r--r--  1 chepner  staff  6 Mar  3 12:47 file_copy.txt
    12890456377 -rw-r--r--  2 chepner  staff  6 Mar  3 12:46 file_link.txt
    

    The -i option adds each entries inode number (the first column) to the output; an inode can be thought of as a unique identifier for a file. In this output, you can see that file_copy.txt is an entirely new file, with a different inode than file.txt. file_link.txt has the exact same inode, meaning that file.txt and file_link.txt are simply two different names for the same thing. The number just before the owner is the link count; file.txt and file_link.txt both refer to a file with a link count of 2.

    When you use rm, you are just removing a link to a file, not the file itself. A file is not removed until the link count is reduced to 0. To demonstrate, we'll remove file.txt and file_copy.txt.

    $ rm file.txt file_copy.txt
    $ ls -li
    total 8
    12890456377 -rw-r--r--  1 chepner  staff  6 Mar  3 12:46 file_link.txt
    

    As you can see, the only link to file_copy is gone, so inode 12890456378 no longer appears in the output. (Whether or not the data is really gone is a matter of file-system implementation.) file_link.txt, though, still refers to the same file as before, but now with a link count of 1, because file.txt was removed.

    Links to a file do not have to appear in the same directory; they can appear in any directory on the same file system, which is the only caveat using this trick. ln will, IIRC, give you an error if you try to create a link to a file on another file system.