NVI
NVI

Reputation: 15075

How do I merge one directory into another using Bash?

I'm looking for shell script that merge files from one directory into another.

Sample:

html/
  a/
    b.html
  index.html

html_new/
  a/
    b2.html
    b.html

Usage:

./mergedirs.sh html html_new

Result:

html/
  a/
    b.html
    b2.html
  index.html

html/a/b.html was replaced by html_new/a/b.html
html/a/b2.html was copied from html_new/a/b2.html
html/index.html was kept untouched

Upvotes: 100

Views: 76450

Answers (8)

CarlG
CarlG

Reputation: 610

Take a look at rsync

rsync --recursive html/ html_new/

Notice that the trailing slash / matters in this case. If you omit it from the source argument, rsync will write the files to html_new/html/ instead of html_new/.

Rsync has got a lot of flags to set so look at rsync manpage for details.

Upvotes: 19

Alberto Salvia Novella
Alberto Salvia Novella

Reputation: 1249

cp --recursive --no-target-directory [--no-clobber] "[IN]" "[OUT]"

  • --no-target-directory: Treat OUT as a normal file, so include files starting with dot (hidden files).

  • --no-clobber: If you don't want to overwrite existing files. Inner dirs are never overwritten anyway.

Upvotes: 1

Flimm
Flimm

Reputation: 151346

cp -RT source/ destination/

All files and directories in source will end up in destination. For example, source/file1 will be copied to destination/file1.

The -T flag stops source/file1 from being copied to destination/source/file1 instead. (Unfortunately, cp on macOS does not support the -T flag.)

Upvotes: 118

Gavster
Gavster

Reputation: 131

Just use rsync - it's a great tool for local file copy and merging in addition to remote copying.

rsync -av /path/to/source_folder/ /path/to/destination_folder/

Note that the trailing slash on the source folder is necessary to copy only the contents of source_folder to the destination. If you leave it off, it will copy the source_folder and it's contents, which is probably not what you are looking for since you want to merge folders.

Upvotes: 8

rowanthorpe
rowanthorpe

Reputation: 435

Even though this question and its accepted answer are ancient, I am adding my answer because the presently existing ones using cp either don't handle some edge-cases or require working interactively. Often edge-cases/scriptability/portability/multiple-sources don't matter though, in which case simplicity wins, and it is better to use cp directly with less flags (as in other answers) to reduce cognitive load - but for those other times (or for a robustly reusable function) this invocation/function is useful, and incidentally isn't bash-specific (I realise this question was about bash though, so that's just a bonus in this case). Some flags can be abbreviated (e.g. with -a), but I have included all explicitly in long-form (except for -R, see below) for the sake of explanation. Obviously just remove any flags if there is some feature you specifically don't want (or you are on a non-posix OS, or your version of cp doesn't process that flag - I tested this on GNU coreutils 8.25's cp):

mergedirs() {
    _retval=0
    _dest="$1"
    shift
    yes | \
        for _src do
            cp -R --no-dereference --preserve=all --force --one-file-system \
                  --no-target-directory "${_src}/" "$_dest" || { _retval=1; break; }
        done 2>/dev/null
    return $_retval
}

mergedirs destination source-1 [source-2 source-3 ...]

Explanation:

  • -R: has subtly different semantics from -r/--recursive on some systems (particularly with respect to special files in source dirs) as explained in this answer
  • --no-dereference: never follow symbolic links in SOURCE
  • --preserve=all: preserve the specified attributes (default: mode,ownership,timestamps), if possible additional attributes: context, links, xattr, all
  • --force: if an existing destination file cannot be opened, remove it and try again
  • --one-file-system: stay on this file system
  • --no-target-directory: treat DEST as a normal file (explained in in this answer, namely: If you do a recursive copy and the source is a directory, then cp -T copies the content of the source into the destination, rather than copying the source itself.)
  • [piped input from yes]: even with --force, in this particular recursive mode cp still asks before clobbering each file, so we achieve non-interactiveness by piping output from yes to it
  • [piped output to /dev/null]: this is to silence the messy string of questions along the lines of cp: overwrite 'xx'?
  • [return-val & early exit]: this ensures the loop exits as soon as there is a failed copy, and returns 1 if there was an error

BTW:

  • A funky new flag which I also use with this on my system is --reflink=auto for doing so-called "light copies" (copy-on-write, with the same speed benefits as hard-linking, and the same size benefits until and in inverse proportion to how much the files diverge in the future). This flag is accepted in recent GNU cp, and does more than a no-op with compatible filesystems on recent Linux kernels. YMWV-a-lot on other systems.

Upvotes: 6

Luke Maurer
Luke Maurer

Reputation: 8255

You probably just want cp -R $1/* $2/ — that's a recursive copy.

(If there might be hidden files (those whose names begin with a dot), you should prefix that command with shopt -s dotglob; to be sure they get matched.)

Upvotes: 91

DVK
DVK

Reputation: 129559

Wouldn't cp -r work?

cp -r html_new/* html

or (since the first version won't copy ".something" files)

cd html_new; cp -r . ../html

Please note that -r reads from pipes if any of the files in the copied directory are pipes. To avoid that, use -R instead.

Upvotes: 2

Brian Clapper
Brian Clapper

Reputation: 26230

cd html
cp -r . /path/to/html_new

Upvotes: 2

Related Questions