84

So, you can use the * as a wild card for all files when using cp within context of a directory. Is there a way to copy all files except x file?

rahmu
  • 20,023

7 Answers7

72

Rsync handles this nicely.

Example copy all: rsync -aP /folder1/* /folder/2

Example copy all with exclusion: rsync -aP --exclude=x /folder1/* /folder2/

The -aP switch:

  • a: Similar to cp -a, recursive, etc.
  • P: Shows progress, a nice feature of rsync.
Tim
  • 6,141
50

In bash you can use extglob:

 $ shopt -s extglob  # to enable extglob
 $ cp !(b*) new_dir/

where !(b*) exclude all b* files.

You can later disable extglob with

 $ shopt -u extglob
heemayl
  • 56,300
rush
  • 27,403
  • Do you know if there's something equivalent for the tcsh shell? – Levon Jun 27 '12 at 01:33
  • Unfortunately I don't. Seems like find is the only way in tcsh: find . -maxdepth 1 ! -name "exclude*" -exec cp -t destination {} \+ – rush Jun 27 '12 at 05:25
13

This isn't a feature of cp, it's a feature of your shell (it expands the * to mean all non-dot files), so the answer depends on which shell you're using. For example, zsh supports this syntax:

$ cp ^x /path/to/destination

Where ^x means "all files except x"

You can also combine selection and de-selection patterns, e.g. to copy all wav files except those containing xyz, you can use:

cp *.wav~*xyz*
Thor
  • 17,182
Michael Mrozek
  • 93,103
  • 40
  • 240
  • 233
6

If you want to copy everything in a folder (including subfolders) to a particular sub-directory:

cp -R $(ls | grep -v '^subdir$') subdir/

Works with sh, bash, zsh (at least).

  • 2
    Convince me this isn't the same intention as cp -R * subdir/ – Chris Davies Oct 28 '15 at 23:51
  • 2
    If you use that command "cp -R * subdir/", bash/zsh tried to copy 'subdir' recurvively. You end up with an error: "name too long (not copied)". – user2707671 Dec 23 '15 at 10:48
  • 2
    Good point. Your suggestion attempts to avoid the warning from cp (not from bash/sh), "cp: cannot copy a directory, ‘subdir’, into itself, ‘subdir/subdir’". The copy does complete correctly, though. Unfortunately your variant breaks with any filename containing a space or shell-sensitive punctuation. See http://unix.stackexchange.com/q/128985/135943 – Chris Davies Dec 23 '15 at 11:41
6

Could also be done in plain old (portable/compatible) bourne shell in a variety of ways with standard tools in a lot less elegant ways than using advanced shell globbing or commands with built-in-exclusion options.

If there are not too many files (and not with names including spaces and/or linebreaks), this could be a way:

cp `ls | egrep -v '^excludename$'` destdir/.

Sure, bash and GNU tools are great and powerful, but they're still not always available. If you intend to put it in a portable script, I would recommend find as in the comment by Rush.

MattBianco
  • 3,704
  • 2
    I find that the last part of your answer just distracts from the topic at hand.

    Besides, "Unix" isn't the gold standard anymore (if it ever were). It just isn't that relevant if something is "Unix" or not anymore, despite the title of this site being "Unix and Linux".

    – Alexander Jun 27 '12 at 08:44
  • 2
    OK. I moved the comment to here instead: Unix is not GNU. I agree that the "unixness" of things is not very interesting, but I still believe in portability and knowing a bit about your history. – MattBianco Jun 27 '12 at 08:55
  • Fully agree with you there. – Alexander Jun 27 '12 at 08:57
4

The best and simple way is using find. Go to the source directory. Then use the following commands.

find . ! -name "*.log" | xargs -i cp -r {} ~/destination_dir

This copies all files except "*.log" files.

  • 1
    Note that find recursively searches all subdirectories by default, so using cp -r is causing to recurse twice on each file. Either use find with -maxdepth 0 or remove -r from the cp. – not2savvy Jul 23 '21 at 08:14
  • Suggestion to exclude both the -r on cp as well as to limit find result to files only: find . -type f ! -name "*.log" | xargs -i cp {} ~/destination_dir – MikeOnline Nov 14 '23 at 17:26
-2

extglob is the best way so far I guess.

Another way is using cp $(ls --ignore=x) subdir/

  • 2
    This will break given any form of special characters whatsoever in the filenames. (Spaces, newlines, $, etc.) Never parse the output of ls. http://unix.stackexchange.com/q/128985/135943 – Wildcard Dec 04 '15 at 06:14