2

Using the below directory tree as an example, what is the best way to move the contents of directory/folderA to directory.

How to overwrite a file if a file with the same name exists in the destination, for example: directory/folderA/2017/06/info.log and directory/2017/06/info.log.

directory
|-- folderA
|   |-- 2017
|   |   |-- 06
|   |
|   |-- 2016
|   |   |-- 12
|   |   |-- 11
|   |   |-- 10
|
|-- 2017
|   |-- 04
|   |-- 05
|   |-- 06
|
|-- 2016
|   |
Greg
  • 1,557
  • 5
  • 24
  • 35
  • You can add the `-f` (force) option to `mv` to overwrite files without asking. – RoseHosting Jul 15 '17 at 02:55
  • No that is incorrect, that does not preserve anything in the destination. The overwrite should only happen IF a file with the same name exists. – Greg Jul 15 '17 at 03:09
  • Have you checked RSync yet? I don't think `mv` suits your requirements. – Tero Kilkanen Jul 15 '17 at 05:52
  • @TeroKilkanen The desired outcome can be achieved using `yes | cp -rf`, the reason why using `mv` is ideal because it doesn't cause the data to be rewritten again (which takes a long time). – Greg Jul 15 '17 at 05:59

2 Answers2

4

You can achieve this with cp -l (--link, hard link files instead of copying):

cp -rfl folderA .
rm -rf folderA

The mv just moves i.e. renames the directory to be your directory. For moving files within the same file system mv uses rename() system call. If the source and target were on different files systems, mv would use cp and rm to accomplish the move, but still first removes the destination, copies hard linked files (-R) but doesn't follow symbolic links (-P):

rm -f destination_path && \
cp -pRP source_file destination && \
rm -rf source_file

While you can't change this behavior of mv, the cp command itself is more flexible, and you really should use it, instead. With cp, the -f causes removing and creation of single files inside the destination instead of removing the whole destination first.

The cp is also limited as it can only either overwrite (-f), preserver (-n) or ask (-i). If you need to compare the files before deciding which one to keep, you'd need rsync.

If you still need to use rename() system call but accomplish your goal of merging the directories as desired, you'd need to write a script that invokes individual rename()s for single files.

Esa Jokinen
  • 43,252
  • 2
  • 75
  • 122
  • The reason why `mv` would be strongly preferred is because it's a lot of data which would take days to `cp`. – Greg Jul 15 '17 at 06:02
  • 1
    I added the solution for you with `cp -l`. It does what you need. Now, the best solution appears first and the rest of the answer explains you why you simply can't use `mv`, what other options you have, and why they all may be more complicated. – Esa Jokinen Jul 15 '17 at 06:16
  • 2
    @Dave To clarify, `cp -l` doesn't actually copy the *data*, it just makes the same file appear in the new directory as well as the old. By doing first `cp -l` and then `rm`, you are doing very nearly the exact same thing as the `mv` command, but you get access to the options for `cp` instead. – Jenny D Jul 15 '17 at 08:38
  • 1
    Good additional explanation for term _hard link_, Jenny! – Esa Jokinen Jul 15 '17 at 08:40
1

First, I created your directory tree with

[root@localhost /tmp]# mkdir -pv directory/folderA/2017/06 directory/folderA/2016/{10..12} directory/2017/{04..06} directory/2016

Then I created your example files with:

[root@localhost /tmp]# touch directory/{,folderA}/2017/06/info.log

Last, I used rsync with the --delete-after to move instead of rsync's default behavior which would create a copy instead:

[root@localhost /tmp]# rsync --delete-after -a directory/folderA/ directory

Note: the trailing '/' after the source directory (ie. directory/folderA/ as opposed to directory/folderA) is required if you want the directories to be deleted after they are copied.

Of course, if you really wanted to type mv to obtain the desired behavior you could create a custom shell function. I'm not advocating this, but it is possible. Sloppy, but possible.

Here's a custom function you could add to your ~/.bashrc:

function mv() {
    if [[ "$@" =~ .*--delete-after.*-av*.* ]]; then
            rsync $@
    else
            command mv $@
    fi; }

Then invoke the updated ~/.bashrc by logging out and logging back in -or- by typing:

[root@localhost /tmp]# exec bash

Now you can test your custom function with either:

[root@localhost /tmp]# mv --delete-after -av directory/folderA/ directory

to get verbose output,

or with:

[root@localhost /tmp]# mv --delete-after -a directory/folderA/ directory

to receive only errors if any are encountered.

As an aside, you can also avoid piping yes to the cp -rf command by issuing your cp command with a preceding backslash like this:

[root@localhost /tmp]# \cp -rf directory/folderA/* directory/

The reason you are piping yes is you likely have an alias for the cp command in your ~/.bashrc such as this:

[root@localhost /tmp]# alias cp='cp -i'

which you can verify by typing:

[root@localhost /tmp]# type cp
Shawn  
  • 33
  • 6