0

I have a directory tree which, among other files, has files which match certain patterns. For the sake of the discussion, let's assume these are files matching *.foo, or *.bar, or baz*. I want to backup inside my zsh-script only files matching these pattern to a new directory.

The seemingly obvious solution,

find fromdir \( -name '*.{foo,bar}' -o -name 'baz*' \) -exec cp {} todir \;

does not work, because the destination directory for, i.e., fromdir/x/y/a.foo does not exist.

I was thinking of using rsync, but I know only how to exclude certain files from being copied, not how to restrict copying.

I can solve the problem by writing a small auxiliary script, mdcp1file, like this:

#!/bin/zsh
set -u
mkdir -p $2/$1:h # Create destination directory if needed
cp $1 $2

and use it in my find command instead of cp. I wonder whether there is an easier way to solve this problem, either by beefing up the -exec of my find, or by using rsync in a clever way.

oguz ismail
  • 1
  • 16
  • 47
  • 69
user1934428
  • 19,864
  • 7
  • 42
  • 87
  • 1
    GNU cp has an option called `--parents`, have you tried that? – oguz ismail Jul 16 '20 at 09:32
  • The man page explains this option as "use full source file name under DIRECTORY". It does not say that it would create the directories if necessary, but I found that it does. I find the man page confusing in this respect. If you write your comment as answer, I will accept it. – user1934428 Jul 16 '20 at 09:40
  • I've already posted an answer that uses `--parents` [here](https://stackoverflow.com/a/62232917/10248678), I don't want to repeat myself. But if you post one I'll upvote it – oguz ismail Jul 16 '20 at 10:00
  • 1
    I see that @kvantour has already posted an answer which also uses `--parents` and gets rid of `find` too, and I accepted this. We could have also closed my question as duplicate, but I think it is pointless doing it at that stage. – user1934428 Jul 16 '20 at 10:38
  • With the zsh tag this wouldn't really be a duplicate of that question. You did the best, have a nice day – oguz ismail Jul 16 '20 at 10:42

1 Answers1

1

As you mention that you make use of zsh, you could just do something like this:

cd /path/to/source/dir
cp --parents **/{*.{foo,bar},baz*}(.) /path/to/destination/dir

Here we make use of:

  • cp --parents: Bash: Copy named files recursively, preserving folder structure
  • **: for matching over multiple directories
  • BRACE EXPANSION: A string of the form foo{xx,yy,zz}bar is expanded to the individual words fooxxbar, fooyybar and foozzbar. Left-to-right order is preserved. This construct may be nested. Commas may be quoted in order to include them literally in a word.
  • Glob Qualifier (.): Patterns used for filename generation may end in a list of qualifiers enclosed in parentheses. The qualifiers specify which filenames that otherwise match the given pattern will be inserted in the argument list. The . selects files only.
kvantour
  • 25,269
  • 4
  • 47
  • 72
  • I like the idea of using globbing instead of `find`. The drawback is, if there are really many files, the arglist could get too long, but for my situation, I think I can do it that way. The crucial point for my question is the use of `--parents`. – user1934428 Jul 16 '20 at 10:40
  • @user1934428 you could also use `rsync` if you want, which fixes the issue with the argument list. – kvantour Jul 16 '20 at 11:01
  • As I wrote, I tried, but did not know how to restrict the copying to only certain patterns. But maybe this should then go into a new question, if necessary, because, technically speaking, I do have a good solution now. – user1934428 Jul 16 '20 at 11:34
  • 1
    @user1934428 maybe this might help you: https://stackoverflow.com/questions/11111562/rsync-copy-over-only-certain-types-of-files-using-include-option – kvantour Jul 16 '20 at 11:49