I want to copy all files in a directory except some files in a specific sub-directory.
I have noticed that cp
command didn't have the --exclude
option.
So, how can I achieve this?

- 22,221
- 10
- 124
- 129

- 16,374
- 12
- 37
- 38
-
24[rsync?](http://stackoverflow.com/questions/1230342/scp-r-but-leave-out-exclude-a-specified-subdirectory) – ajreal Jan 03 '11 at 15:56
-
11`tar -c | tar -x` ? – mvds Jan 03 '11 at 16:00
-
2@mvds, agree with you, using tar with '--exclude' is a good idea. – Huang F. Lei Jan 03 '11 at 16:38
-
4Possible duplicate of [Copy folder recursively, excluding some folders](http://stackoverflow.com/questions/2193584/copy-folder-recursively-excluding-some-folders) – phuclv Jan 06 '16 at 09:00
20 Answers
rsync
is fast and easy:
rsync -av --progress sourcefolder /destinationfolder --exclude thefoldertoexclude
You can use --exclude
multiples times.
rsync -av --progress sourcefolder /destinationfolder --exclude thefoldertoexclude --exclude anotherfoldertoexclude
Note that the dir thefoldertoexclude
after --exclude
option is relative to the sourcefolder
, i.e., sourcefolder/thefoldertoexclude
.
Also you can add -n
for dry run to see what will be copied before performing real operation, and if everything is ok, remove -n
from command line.
-
5Agreed, you can't beat the simplicity and power of `--exclude` – Matthew Wilcoxson Feb 14 '13 at 17:51
-
46is the `thefoldertoexclude` relative to the `sourcefolder` or the current working dir? thanks – Beebee Aug 18 '13 at 12:20
-
55It's relative to the source folder. This will exclude the folder source/.git from being copied. rsync -r --exclude '.git' source target – orkoden Nov 14 '13 at 18:55
-
23Maybe I'm wrong but I think it's a good practice to add "switches" before "parameters". Also the man page of rsync reports --exclude usable as with the "=" syntax or without. So to standarize across operating systems, I'd use `rsync -av --progress --exclude="thefoldertoexclude" sourcefolder /destinationfolder` - anyway upvote for the rsync instead of the find, as you can easily use absolute paths for the source while in the find it's trickier as it uses the `{}` in the dst. – Xavi Montero Feb 11 '14 at 17:30
-
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:56
-
-
@XaviMontero definitely not, as you add many of those switches, it will get lost what is the actual folder being copied. imaging having, 5 exclude switches. Some args are good to have them at the end, i think – thebugfinder Aug 28 '15 at 07:54
-
1I think slash at wrong place,`rsync -av --progress sourcefolder/ destinationfolder --exclude thefoldertoexclude` – Masker Oct 09 '15 at 09:14
-
1@mamun Of course e.g. from remote server to local machine: `$ rsync user@example.com:/var/www/mypage /var/www/mylocalpage/` or from local to remote `$ rsync /var/www/mylocalpage/ user@example.com:/var/www/mypage` – sobi3ch Nov 18 '15 at 21:45
-
1Unfortunately I found that `rsync` are ~4x slower then `cp` command :( at least on my VM even when I'm including big `.git` directory in `cp` task and excluding in `rsync`. – sobi3ch Nov 18 '15 at 21:46
-
-
1This command is purely magic, it doesn't overwrite the unchanged files, it also shows you the time and the transfer speed, also shows you which file where copied only the different ones !! – Tiberiu Popescu May 09 '17 at 11:17
-
-
@Yugi you can add --exclude multiple times for another folders – Oleg Neumyvakin Mar 29 '18 at 15:19
-
@orkoden Here on Ubuntu exclude works relative to the current working dir (which is really unintuitive) – DarkTrick Feb 03 '21 at 12:19
-
2@XaviMontero and if you want to exclude multiple folders you can use rsync like this rsync -av --progress --exclude={"thefoldertoexclude","anotherfolder","folder2"} sourcefolder /destinationfolder – Hódos Gábor Feb 04 '21 at 13:18
-
Ahh good to know @HódosGábor. I always gave multiple `--exclude` options by typing them all. Nice compact format! – Xavi Montero Feb 05 '21 at 08:10
-
@hank what is the use of `-a` in above as per the man page it is for archiving or for appending data onto shorter files? – Kasun Siyambalapitiya May 01 '21 at 10:16
-
6`--exclude /node_modules/` will exclude node_modules anywhere in the source tree. So powerful while copying a bunch of JavaScript/Node.js project folders. – Fahad Munir Jul 22 '21 at 09:45
-
The exclude explanation appears to be incorrect. According to the man page it will "exclude files matching PATTERN" so that pattern doesn't have to be relative to the source folder, it's simply a pattern – Randall Coding Aug 02 '23 at 08:43
Well, if exclusion of certain filename patterns had to be performed by every unix-ish file utility (like cp, mv, rm, tar, rsync, scp, ...), an immense duplication of effort would occur. Instead, such things can be done as part of globbing, i.e. by your shell.
bash
man 1 bash
, / extglob.
Example:
$ shopt -s extglob $ echo images/* images/004.bmp images/033.jpg images/1276338351183.jpg images/2252.png $ echo images/!(*.jpg) images/004.bmp images/2252.png
So you just put a pattern inside !()
, and it negates the match. The pattern can be arbitrarily complex, starting from enumeration of individual paths (as Vanwaril shows in another answer): !(filename1|path2|etc3)
, to regex-like things with stars and character classes. Refer to the manpage for details.
zsh
man 1 zshexpn
, / filename generation.
You can do setopt KSH_GLOB
and use bash-like patterns. Or,
% setopt EXTENDED_GLOB % echo images/* images/004.bmp images/033.jpg images/1276338351183.jpg images/2252.png % echo images/*~*.jpg images/004.bmp images/2252.png
So x~y
matches pattern x
, but excludes pattern y
. Once again, for full details refer to manpage.
fishnew!
The fish shell has a much prettier answer to this:
cp (string match -v '*.excluded.names' -- srcdir/*) destdir
Bonus pro-tip
Type cp *
, hit CtrlX* and just see what happens. it's not harmful I promise
-
1@MikhailGolubtsov perhaps that's because globbing is not recursive and works one level at a time. Edited out. P.S: it works in `zsh` though. – ulidtko Jul 11 '15 at 02:16
-
8Nice pro-tip! This way you can remove single items easily. Thanks a lot! – taffit May 01 '18 at 19:42
-
2BTW, to turn off extended pattern matching features in Bash, run `setopt -u extglob`. – Rockallite Dec 10 '19 at 00:45
-
1"... an immense duplication of effort..." shouldn't it be just a one-liner: exclude paths from the list that match a regex? How the file manipulating utilities like `cp` don't support this most simple and straightforward use case out of the box is beyond me. Thanks for the tip though! – ayorgo Sep 30 '20 at 07:26
-
@ayorgo well yes it "should" — but in C, a oneliner can't do much: multiply some ints and maybe move a pointer, that's it. Even ignoring the source level, regex matching in C involves additional library dependency and additional machine code output — now *multiply* this by the number of commands, and you've got nontrivial (unbounded?..) overhead. At least that's my understanding why it was "refactored" to the shell; I can totally relate to the subpar UI aspect of it, but hopefully you can also see the technical justification now. Best wishes! – ulidtko Sep 30 '20 at 13:09
-
-
@t7e in that case, I'm afraid, you're restricted to `find ... | xargs`. – ulidtko May 24 '22 at 12:39
Why use rsync
when you can do:
find . -type f -not -iname '*/not-from-here/*' -exec cp '{}' '/dest/{}' ';'
This assumes the target directory structure being the same as the source's.

- 33,871
- 11
- 91
- 99
-
13I think you need the `-path` argument to test path hierarchies, not -iname – James Murty Jun 05 '12 at 00:34
-
6And you'll also need a semi-colon at the end: `find . -type f -not -path '*/not-from-here/*' -exec cp '{}' '/dest/{}' \;` – Matthew Wilcoxson Feb 14 '13 at 17:13
-
-
1Wow, it won't let me: "Edits must be at least 6 characters" ! – Matthew Wilcoxson Feb 14 '13 at 17:54
-
1@MatthewWilcoxson Meh. Those restrictions will be lifted, as soon as you gain a little more rep. I edited the answer accordingly. Thanks again! – Linus Kleen Feb 14 '13 at 19:24
-
Why, thank you, @Henning for pointing that out. Although this answer is now more than three years old, it never occurred to me that `rsync` might actually be a shorter command. Wait. No. It did. That's why I mentioned it. Now it's up to you to supply that answer right here. Just because mine is the accepted answer doesn't mean yours could outscore it. Go for it. – Linus Kleen Mar 06 '14 at 19:29
-
3@Henning why not `rsync`? Coz it may be not present in the system! while `find`, `cp` is always on their places. Or you from kind of guys, who installed 2gigs of stuff to do simple things? – Reishin Jul 06 '18 at 14:20
-
1
-
1@xuhdev Hence the disclaimer in the answer. It only works when the target directory structure is the same as the source's. – Linus Kleen Oct 04 '18 at 07:40
-
My fix for that - somewhere below: https://stackoverflow.com/a/65485164/1707015 – qräbnö Dec 28 '20 at 23:03
-
cp -r `ls -A | grep -v "c"` $HOME/

- 80,836
- 20
- 110
- 183

- 503
- 4
- 2
-
1
-
Made a shell function which simplifies the usage for custom source path and exclusion of just one file or directory: `# $1 = source path # $2 = destination path # $3 = filter copy_from_source_to_destination_except_filter() { cp -r $(ls -A $1 | grep -v -w $3 | awk -v path=$1 '{printf "%s/%s ", path, $1}') $2 }` – ElectRocnic May 05 '19 at 16:28
-
2
-
1@Sérgio I haven't tested it but `cp -r "$(ls -A | grep -v "c")" $HOME/` should work. The command in the answer fails because there `cp` operates on the output of `ls -A | grep -v "c"`, which is unquoted and therefore breaks on spaces. `"$(…)"` is the same as ``"`…`"`` but easier on the eyes. – Arch Stanton Oct 23 '20 at 17:10
The easiest way I found, where you can copy all the files excluding files and folders just by adding their names in the parentheses:
shopt -s extglob
cp -r !(Filename1 | FoldernameX | Filename2) Dest/

- 3,377
- 2
- 30
- 40

- 5,589
- 5
- 45
- 80
-
13
-
9
-
1@geneorama This happens if history substitution is enabled. https://serverfault.com/a/208414/352016 – mbomb007 Oct 01 '20 at 17:16
-
1
It's relative to the source directory.
This will exclude the directory source/.git
from being copied.
rsync -r --exclude '.git' source target

- 1,916
- 6
- 27
- 36

- 291
- 3
- 3
-
What is the difference/improvement compared to the top answer? – reducing activity Jan 13 '20 at 13:06
-
1
-
I feel like the '-a' in the first answer is better than plain old -r: https://explainshell.com/explain?cmd=rsync+-a – Hawkeye Parker Jul 27 '21 at 18:09
-
@reducingactivity nothing much but simple to digest as the expression is shorter, just my personal preference – Goran B. Sep 16 '21 at 20:50
Expanding on mvds’s comment, this works for me
cd dotfiles
tar -c --exclude .git --exclude README . | tar -x -C ~/dotfiles2
-
The nice thing about tar, you can use exclude.tag files to ignore directories https://stackoverflow.com/a/13280610/722796 also https://www.gnu.org/software/tar/manual/html_node/exclude.html – PJ Brunet May 09 '21 at 00:25
rsync
is actually quite tricky. have to do multiple tests to make it work.
Let's say you want to copy /var/www/html
to /var/www/dev
but need to exclude /var/www/html/site/video/
directory maybe due to its size. The command would be:
rsync -av --exclude 'sites/video' /var/www/html/ /var/www/dev
Some caveat:
- The last slash
/
in the source is needed, otherwise it will also copy the source directory rather than its content and becomes/var/www/dev/html/xxxx
, which maybe is not what you want. The the
--exclude
path is relative to the source directly. Even if you put full absolute path, it will not work.-v
is for verbose,-a
is for archive mode which means you want recursion and want to preserve almost everything.

- 9,388
- 1
- 65
- 59
-
a simple solution that take cares of special characters and white spaces – Sérgio May 28 '19 at 01:51
-
1Thanks for explaining parameters, unlike the current top answer! – reducing activity Jan 13 '20 at 13:07
-
-
1@ddzzbbwwmm You probably figured it out by now, but for posterity's sake: you can add multiple `--exclude` flags, like: `--exclude 'foo' --exclude 'bar'` – Dalbergia Sep 02 '21 at 21:48
cp -rv `ls -A | grep -vE "dirToExclude|targetDir"` targetDir
Edit: forgot to exclude the target path as well (otherwise it would recursively copy).

- 778
- 5
- 10
rsync
rsync -r --verbose --exclude 'exclude_pattern' ./* /to/where/
and first try it with -n option to see what is going to be copied

- 99
- 1
- 1
-
1What is the difference/improvement compared to the top answer? – reducing activity Jan 13 '20 at 13:06
I assume you're using bash or dash. Would this work?
shopt -s extglob # sets extended pattern matching options in the bash shell
cp $(ls -laR !(subdir/file1|file2|subdir2/file3)) destination
Doing an ls excluding the files you don't want, and using that as the first argument for cp

- 7,380
- 6
- 33
- 47
-
13You can skip the extra `ls` and simply do `cp !(file1|file1) dest`. – Shawn Chin Jan 12 '11 at 15:08
-
Do not use -laR. it add string that interfere with cp. `cp $(ls folder/!exclude_folder0|exclude_folder1)) dest` – LAL Dec 09 '15 at 21:14
Just move it temporally into a hidden directory (and rename it after, if wanted).
mkdir .hiddendir
cp * .hiddendir -R
mv .hiddendir realdirname

- 4,473
- 1
- 44
- 33
-
1Not pretty maybe – but this is the only option I’ve found here which works with `cp` and a standard POSIX shell like `sh`. – tomekwi Sep 01 '15 at 11:38
-
1This answer is dramatically underrated. This is the most compatible, easiest to read and easy to understand answer. Kudos, I don't know why I didn't think of it. – Robert Talada Jun 13 '20 at 03:37
-
-
1Obvious drawback is that you might be avoiding copying something because it's too big. – Robino Jun 28 '21 at 07:26
This is a modification of Linus Kleen's answer. His answer didn't work for me because there would be a . added in front of the file path which cp doesn't like (the path would look like source/.destination/file).
This command worked for me:
find . -type f -not -path '*/exlude-path/*' -exec cp --parents '{}' '/destination/' \;
the --parents command preserves the directory structure.

- 432
- 4
- 12
rsync
went unavailable for us. Below is an alternative that works.
tar -cf - --exclude='./folder' --exclude='./file.tar' ./source_directory | tar -xf - -C ./destination_directory

- 363
- 2
- 8
ls -I "filename1" -I "filename2" | xargs cp -rf -t destdir
The first part ls
all the files but hidden specific files with flag -I
. The output of ls
is used as standard input for the second part. xargs
build and execute command cp -rf -t destdir
from standard input. the flag -r
means copy directories recursively, -f
means copy files forcibly which will overwrite the files in the destdir
, -t
specify the destination directory copy to.

- 41
- 4
cp -r `ls -A | grep -v "Excluded_File_or_folder"` ../$target_location -v

- 323
- 3
- 10

- 29
- 2
I use a "do while" loop to read the output of the find command. In this example, I am matching (rather than excluding) certain patterns since there are a more limited number of pattern matches that I want than that I don't want. You could reverse the logic with a -not
in front of the -iname
flags:
find . -type f -iname "*.flac" -o -print0 -iname "*.mp3" -print0 -o -iname "*.wav" -print0 -o -iname "*.aac" -print0 -o -iname "*.wma" -print0 | while read -d $'\0' file; do cp -ruv "$file" "/media/wd/network_sync/music/$file"; done
I use the above to copy all music type files that are newer on my server than the files on a Western Digital TV Live Hub that I have mounted at /media/wd
. I use the above because I have a lot of DVD files, mpegs, etc. that I want to exclude AND because for some reason rsync looks like it is copying, but after I look at the wd device, the files are not there despite no errors during the rsync with this command:
rsync -av --progress --exclude=*.VOB --exclude=*.avi --exclude=*.mkv --exclude=*.ts --exclude=*.mpg --exclude=*.iso --exclude=*ar --exclude=*.vob --exclude=*.BUP --exclude=*.cdi --exclude=*.ISO --exclude=*.shn --exclude=*.MPG --exclude=*.AVI --exclude=*.DAT --exclude=*.img --exclude=*.nrg --exclude=*.cdr --exclude=*.bin --exclude=*.MOV --exclude=*.goutputs* --exclude=*.flv --exclude=*.mov --exclude=*.m2ts --exclude=*.cdg --exclude=*.IFO --exclude=*.asf --exclude=*.ite /media/2TB\ Data/data/music/* /media/wd/network_sync/music/

- 2,241
- 2
- 18
- 28

- 19
- 2
10 years late. Credits to Linus Kleen.
I hate rsync
! ;) So why not use find
and cp
? And with this answer also mkdir
to create a non-existent folder structure.
cd /source_folder/ && find . -type d -not -path '*/not-from-here/*' -print -exec mkdir -p '/destination_folder/{}' \;
cd /source_folder/ && find . -type f -not -path '*/not-from-here/*' -print -exec cp -au '{}' '/destination_folder/{}' \;
It looks like cd
ìs necessary to concat relative paths with find
.
mkdir -p
will create all subfolders and will not complain when a folder already exists.
Housten we have the next problem. What happens when someone creates a new folder with a new file in the middle of it? Exactly: it will fail for these new files. (Solution: just run it again! :)) The solution to put everything into one find
command seems difficult.
For clean-up: https://unix.stackexchange.com/q/627218/239596

- 2,722
- 27
- 40
-
Gave it a try. Current version of your answer creates in the first step directories. Because this step currently contains `-not -path '*/not-from-here/*'`, it will create directory `./not-from-here`. Probably, this is not intended. Therefore, for the first step (directory creation), you probably want `-not -path '*/log'` instead. – Abdull Apr 13 '21 at 18:05
mv tobecopied/tobeexcluded .
cp -r tobecopied dest/
mv tobeexcluded tobecopied/

- 80,836
- 20
- 110
- 183

- 27
- 1