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?
-
2I 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
-
2I 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
-
2This 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
-
3Maybe 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
-
2Note 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 Answers
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*

- 12,235
- 2
- 29
- 36

- 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
-
16I 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
-
2For 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
-
-
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
-
-
3rsync seems to be extremely slow compared to cp ? At least this was my experience. – Kojo Aug 29 '16 at 15:07
-
4For 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
-
1It 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
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.

- 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
-
4This 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
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.

- 346,391
- 90
- 374
- 439
-
-
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
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
andsource/
create different results:source
— copy the contents of source into destination.source/
— copy the folder source into destination.
- To exclude many files:
--exclude-from=FILE
—FILE
is the name of a file containing other files or directories to exclude.
--exclude
may also contain wildcards:- e.g.
--exclude=*/.svn*
- e.g.
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

- 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
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

- 327,991
- 56
- 259
- 343
Similar to Jeff's idea (untested):
find . -name * -print0 | grep -v "exclude" | xargs -0 -I {} cp -a {} destination/

- 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
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.

- 2,901
- 5
- 30
- 50
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"

- 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
-
2thanks @EricBringley for clarifying the shortcomings of Steve's answer. (He did say it was *untested* though.) – go2null Mar 01 '19 at 13:13
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...

- 2,210
- 1
- 15
- 15
-
2This 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