223

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?

trobrock
  • 46,549
  • 11
  • 40
  • 46
  • 2
    I imagine something like find . -name * piped to grep /v "exclude-pattern" to filter the ones you don't want and then piped to cp to do the copy. – i_am_jorf Feb 03 '10 at 16:43
  • 2
    I was trying to do something like that, but couldnt figure out how to use cp with a pipe – trobrock Feb 03 '10 at 16:45
  • 2
    This should probably go to super user. The command you're looking for is xargs. You could also do something like two tar's connected by a pipe. – Kyle Butt Feb 03 '10 at 16:48
  • 3
    Maybe its late and it doesnt answer the question accurately but here's a tip: If you want to exclude only immediate children of the directory you could take advantage of bash pattern matching, e.g. `cp -R !(dir1|dir2) path/to/destination` – Boris D. Teoharov Aug 14 '14 at 18:02
  • 2
    Note that the `!(dir1|dir2)` pattern requires `extglob` to be turned on (`shopt -s extglob` to turn it on). – Boris D. Teoharov Aug 20 '14 at 19:07
  • is there really no way to do this with the `cp` command? fml – Alexander Mills May 26 '18 at 23:53

9 Answers9

376

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*

Xiao
  • 12,235
  • 2
  • 29
  • 36
Kaleb Pederson
  • 45,767
  • 19
  • 102
  • 147
  • If you - like me - want to recursively copy an entire structure and only keep a certain type of files then you can also use rsync: rsync -rav --include=*/ --include="*.txt" --exclude=* test/ mytest This will take all .txt files from source directory and copy them to destination directory with the structure intact. – slott Apr 14 '13 at 15:40
  • 16
    I suggest to add the --dry-run in order to check which files are going to be copied. – loretoparisi Sep 20 '13 at 10:14
  • I am sorry but rsync is not installed by default on any system I know of, tar OTOH is. – AmokHuginnsson Jul 18 '14 at 17:57
  • 2
    @AmokHuginnsson - What systems are you using? Rsync is included by default in all mainstream Linux distros I know of, including RHEL, CentOS, Debian, and Ubuntu, and I believe it's in FreeBSD as well. – siliconrockstar Jan 30 '15 at 19:50
  • @siliconrockstar It does not matter if it is included in given distro, what matters is if it is installed with default install. rsync is not installed with default distro install on non of the systems you specified. – AmokHuginnsson Feb 01 '15 at 12:56
  • 2
    For RHEL derived distros: yum install rsync, or on Debian-based releases: apt-get install rsync . Unless you're building your server from absolute base on your own hardware, this is a non-issue. rsync is installed by default on my Amazon EC2 boxes, as well and my boxes from ZeroLag and RackSpace. – siliconrockstar Feb 02 '15 at 14:09
  • On 'rsync not being available': It's not 1999 anymore. – sjas Jul 23 '15 at 15:27
  • if you are using babun on windows don't forget to use `/cygdrive/c/path-to-file`, instead of the simple c:/path-to-file. cygwin interprets "c:/" as a external drive and you cant copy from remote to remote in rsync. – Edu Ruiz Sep 28 '15 at 18:13
  • `.` represents current directory. – Priya Ranjan Singh Jul 14 '16 at 13:37
  • 3
    rsync seems to be extremely slow compared to cp ? At least this was my experience. – Kojo Aug 29 '16 at 15:07
  • 4
    For example to ignore the git dir: `rsync -av --exclude='.git/' ../old-repo/ .` – nycynik Apr 05 '17 at 18:28
  • it's useful to increase verbosity to debug why certain files/folders were included/excludes, e.g. `rsync -avvm ...` – metakermit Aug 02 '17 at 14:41
  • Note that you can use a pattern in exclude like: `--exclude *_excludeMe source dest` if you have some regular pattern of things you want to exclude, say stuff you git ignore. – Richard J. Acton Nov 05 '18 at 16:28
  • I suppose this could be very useful to someone. In case the source or destination contains spaces, rsync fails. But it is easily solved by adding the -s or --protect-args option (they are the same). Here is an example: `rsync -av --protect-args --exclude='path1/to/exclude' --exclude='path2/to/exclude' source\ with\ spaces destination\ with\ spaces` – Kloster Matias Jun 08 '21 at 01:18
  • 1
    It is also very useful to note that the exclude patterns are in relation to the source directory, not your working directory. Assuming a folder structure of `cwd/to_copy/ignore_me`, and you are in the `cwd` directory, then you'd want `rsync -av --exclude='ignore_me' to_copy destination` – Don Mar 24 '22 at 14:19
49

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.

Kyle Butt
  • 9,340
  • 3
  • 22
  • 15
  • This approach unnecessarily first tars the target source (and exludes particular directories in the archive) and then untars it at the target. Not recommended! – Waldheri Jan 14 '16 at 09:45
  • 7
    @Waldheri you are wrong. This is the best solution. It does exactly what OP requested and it works on default install of most of the *nix like OSes. Taring and untaring is done on the fly with no file system artefact (in memory), cost of this tar+untar is negligible. – AmokHuginnsson Feb 08 '16 at 18:42
  • @WouterDonders Tar is minimal overhead. It doesn't apply compression. – Kyle Butt Apr 27 '20 at 23:19
  • 4
    This is perfect when `rsync` is not available in your container and you don't want to bother with installing it. – Dániel Kis-Nagy Oct 19 '21 at 09:17
  • `rsync` went unavailable for us. So i updated the above tar a bit and this is what i came up with. `tar -cf - --exclude='./folder' --exclude='./file.tar' ./source_directory | tar -xf - -C ./destination_directory` – Panduka May 04 '22 at 17:20
12

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.
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Props for locating a highly relevant example directly from a manpage. – David M Oct 27 '16 at 19:29
  • Looks good indeed! This is also available [in the online docs](https://www.gnu.org/software/findutils/manual/html_node/find_html/Copying-A-Subset-of-Files.html). Unfortunately `cpio` hasn't been packaged for MSYS2 yet. – underscore_d Apr 09 '18 at 12:22
7

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
Jack
  • 397
  • 4
  • 13
  • 1
    [REVIEW] nice layout Jack.. but the code was already submitted above 10 years ago :d so I have to downvote, despite the creative icons you have posted ! – Goodies Aug 21 '20 at 00:09
  • 1
    @Goodies when I read the original post, I was confused by the layout and explanation so that's why I reformatted it (thanks for appreciating the icons and layout!) . If I wanted to improve the original answer, should I try to edit it (the one from ten years ago)? I just find edits take a while to be approved and I thought my submission was unique enough to be considered different. – Jack Sep 10 '20 at 06:14
  • right.. upvote your comment.. btw I am a beginner mod.. I can edit things, but I only do that with recent titles. Better keep questions and answers layout intact, unless there is really an error. – Goodies Sep 12 '20 at 17:30
4

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

ghostdog74
  • 327,991
  • 56
  • 259
  • 343
3

Similar to Jeff's idea (untested):

find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/
Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • Sorry, but I really don't get why 5 people upvoted this when it was admittedly untested and doesn't seem to work on a simple test: I tried this in a subdir of `/usr/share/icons` and immediately got `find: paths must precede expression: 22x22` where the latter is one of the subdirs therein. My command was `find . -name * -print0 | grep -v "scalable" | xargs -0 -I {} cp -a {} /z/test/` (admittedly, I'm on MSYS2, so really in `/mingw64/share/icons/Adwaita`, but I can't see how this is MSYS2's fault) – underscore_d Apr 09 '18 at 12:16
2

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.

Johannes
  • 2,901
  • 5
  • 30
  • 50
1

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"
go2null
  • 2,080
  • 1
  • 21
  • 17
  • It seems unhelpful to say that someone's answer "would fail" without explaining what is wrong with it and how you fix that... – underscore_d Apr 09 '18 at 11:47
  • @underscore_d : true, in hindsight, esp as I can't now remember what failed :-( – go2null May 14 '18 at 21:10
  • Multiple things: (1) it copies files multiple times and (2) the logic still copies files to be excluded. Run through the loops using i=foo: it will be copied 3 times instead of 4 for any other file e.g. i=test.txt. – Eric Bringley Nov 30 '18 at 12:04
  • 2
    thanks @EricBringley for clarifying the shortcomings of Steve's answer. (He did say it was *untested* though.) – go2null Mar 01 '19 at 13:13
0
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...

Steve Lazaridis
  • 2,210
  • 1
  • 15
  • 15
  • 2
    This is incorrect. A few problems: As written, it will copy a file that is not supposed to be excluded multiple times (the number of items to be excluded which in this case is 4). Even if you do attempt to copy 'foo', the first item in the exclude list, it will still be copied over when you get to x=bar and i is still foo. If you insist on doing this without pre-existing tools (e.g. rsync), move the copy to an if statement outside the 'for x in...' loop and make the 'for x...' loop change the logical statement in the if(true) copy file. This will stop you from copying multiple times. – Eric Bringley Nov 30 '18 at 12:01