Copy folder recursively, excluding some folders

BashUnixShellScripting

Bash Problem Overview


I am trying to write a simple bash script that will copy the entire contents of a folder including hidden files and folders into another folder, but I want to exclude certain specific folders. How could I achieve this?

Bash Solutions


Solution 1 - Bash

Use rsync:

rsync -av --exclude='path1/to/exclude' --exclude='path2/to/exclude' source destination

Note that using source and source/ are different. A trailing slash means to copy the contents of the folder source into destination. Without the trailing slash, it means copy the folder source into destination.

Alternatively, if you have lots of directories (or files) to exclude, you can use --exclude-from=FILE, where FILE is the name of a file containing files or directories to exclude.

--exclude may also contain wildcards, such as --exclude=*/.svn*

Solution 2 - Bash

Use tar along with a pipe.

cd /source_directory
tar cf - --exclude=dir_to_exclude . | (cd /destination && tar xvf - )

You can even use this technique across ssh.

Solution 3 - Bash

You can use find with the -prune option.

An example from man find:

cd /source-dir
find . -name .snapshot -prune -o ( ! -name *~ -print0 )|
cpio -pmd0 /dest-dir

   This command copies the contents of /source-dir to /dest-dir, but omits
   files  and directories named .snapshot (and anything in them).  It also
   omits files or directories whose name ends in ~,  but  not  their  con‐
   tents.  The construct -prune -o \( ... -print0 \) is quite common.  The
   idea here is that the expression before -prune matches things which are
   to  be  pruned.  However, the -prune action itself returns true, so the
   following -o ensures that the right hand side  is  evaluated  only  for
   those  directories  which didn't get pruned (the contents of the pruned
   directories are not even visited, so their  contents  are  irrelevant).
   The  expression on the right hand side of the -o is in parentheses only
   for clarity.  It emphasises that the -print0 action  takes  place  only
   for  things  that  didn't  have  -prune  applied  to them.  Because the
   default `and' condition between tests binds more tightly than -o,  this
   is  the  default anyway, but the parentheses help to show what is going
   on.

Solution 4 - Bash

you can use tar, with --exclude option , and then untar it in destination. eg

cd /source_directory
tar cvf test.tar --exclude=dir_to_exclude *
mv test.tar /destination 
cd /destination  
tar xvf test.tar

see the man page of tar for more info

Solution 5 - Bash

Similar to Jeff's idea (untested):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/

Solution 6 - Bash

 Quick Start

Run:

rsync -av --exclude='path1/in/source' --exclude='path2/in/source' [source]/ [destination]

 Notes

  • -avr will create a new directory named [destination].
  • source and source/ create different results:
    • source — copy the contents of source into destination.
    • source/ — copy the folder source into destination.
  • To exclude many files:
    • --exclude-from=FILEFILE is the name of a file containing other files or directories to exclude.
  • --exclude may also contain wildcards:
    • e.g. --exclude=*/.svn*

Modified from: https://stackoverflow.com/a/2194500/749232


 Example

Starting folder structure:

.
├── destination
└── source
    ├── fileToCopy.rtf
    └── fileToExclude.rtf

Run:

rsync -av --exclude='fileToCopy.rtf' source/ destination

Ending folder structure:

.
├── destination
│   └── fileToExclude.rtf
└── source
    ├── fileToCopy.rtf
    └── fileToExclude.rtf

Solution 7 - Bash

Simple solution (but I would still prefer the bash pattern matching from the top comments):

touch /path/to/target/.git
cp -n -ax * /path/to/target/
rm /path/to/target/.git

This exploits the -n option of cp, which forces cp to not overwrite existing targets.

Drawback: Works with GNU cp. If you don't have GNU cp, then the cp operation might return an error code (1), which is annoying because then you can't tell if it was a real failure.

Solution 8 - Bash

EXCLUDE="foo bar blah jah"                                                                             
DEST=$1

for i in *
do
    for x in $EXCLUDE
    do  
        if [ $x != $i ]; then
            cp -a $i $DEST
        fi  
    done
done

Untested...

Solution 9 - Bash

inspired by @SteveLazaridis's answer, which would fail, here is a POSIX shell function - just copy and paste into a file named cpx in yout $PATH and make it executible (chmod a+x cpr). [Source is now maintained in my GitLab.

#!/bin/sh

# usage: cpx [-n|--dry-run] "from_path" "to_path" "newline_separated_exclude_list"
# limitations: only excludes from "from_path", not it's subdirectories

cpx() {
# run in subshell to avoid collisions
  (_CopyWithExclude "$@")
}

_CopyWithExclude() {
  case "$1" in
    -n|--dry-run) { DryRun='echo'; shift; } ;;
  esac

  from="$1"
  to="$2"
  exclude="$3"

  $DryRun mkdir -p "$to"

  if [ -z "$exclude" ]; then
	  cp "$from" "$to"
	  return
  fi

  ls -A1 "$from" \
	| while IFS= read -r f; do
	    unset excluded
	    if [ -n "$exclude" ]; then
  	      for x in $(printf "$exclude"); do
          if [ "$f" = "$x" ]; then
              excluded=1
        	  break
          fi
  	      done
	    fi
	    f="${f#$from/}"
	    if [ -z "$excluded" ]; then
  	      $DryRun cp -R "$f" "$to"
	    else
  	      [ -n "$DryRun" ] && echo "skip '$f'"
	    fi
      done
}

# Do not execute if being sourced
[ "${0#*cpx}" != "$0" ] && cpx "$@"

Example usage

EXCLUDE="
.git
my_secret_stuff
"
cpr "$HOME/my_stuff" "/media/usb" "$EXCLUDE"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontrobrockView Question on Stackoverflow
Solution 1 - BashKaleb PedersonView Answer on Stackoverflow
Solution 2 - BashKyle ButtView Answer on Stackoverflow
Solution 3 - BashDennis WilliamsonView Answer on Stackoverflow
Solution 4 - Bashghostdog74View Answer on Stackoverflow
Solution 5 - BashMatthew FlaschenView Answer on Stackoverflow
Solution 6 - BashJackView Answer on Stackoverflow
Solution 7 - BashJohannesView Answer on Stackoverflow
Solution 8 - BashSteve LazaridisView Answer on Stackoverflow
Solution 9 - Bashgo2nullView Answer on Stackoverflow