cp --parents and rsync copy the same files into two different trees. find src -name '*.txt' -exec cp --parents {} dst/ \; lands them at dst/src/a/b/file; the rsync include/exclude version lands the same files at dst/a/b/file — no src segment. Pick the wrong one and you get an extra level of nesting nobody warned you about.
1. How do I copy files matching a pattern while keeping the folder structure?
$ clai copy all txt files from src to dst, keeping the folder structure→ find src -name '*.txt' -exec cp --parents {} dst/ \;
--parents recreates the full path to the file inside the destination. The result is dst/src/a/b/file: src itself ends up inside the destination, because it's part of the path you handed to find. That's the surprise this article is about.
2. Why does rsync build a different destination tree for the same job?
$ clai copy only the txt files from src to dst2, keeping the nesting→ rsync -a --include='*/' --include='*.txt' --exclude='*' src/ dst2/
Same files, different result: dst2/a/b/file, no src level. The trailing slash on src/ means "the contents of the directory," not the directory itself. Rule order matters too — allow traversal into every directory with --include='*/' first, then the files you actually want, and only then exclude the rest. Move --exclude='*' to the front and nothing gets copied at all.
3. How do I check what rsync will copy before it runs?
$ clai show me what would get copied, but copy nothing→ rsync -avn --include='*/' --include='*.txt' --exclude='*' src/ dst2/
-n is a dry run. With include/exclude rules that aren't trivial, skipping it means finding out the result after the fact instead of before. Run -n first, every time.
4. How do I copy files to a remote server with the same structure?
$ clai move all txt files to a remote server, preserving the structure→ find src -name '*.txt' -print0 | tar --null -cf - -T - | ssh user@host 'tar -xf - -C /dest'
tar streams the files and unpacks them on the other end, structure intact as-is. -print0 and --null protect against spaces in filenames. One pass over the network beats a thousand individual copies.
5. How do I copy without overwriting newer files at the destination?
$ clai copy the files but don't touch the ones in the destination that are newer→ rsync -au src/ dst/
-u skips any file whose destination copy has a newer modification time. Needed when sync runs in both directions, or when the destination already has edits you don't want clobbered.
6. How do I recreate just the directory tree, without the files?
$ clai create the same folder tree as src in dst, without the files→ rsync -a --include='*/' --exclude='*' src/ dst/
Same rules, minus the line that lets files through. Useful for prepping a tree to receive output — before a batch image conversion, for instance.
Gotchas
- The trailing slash on the source changes everything for rsync.
src/copies the contents;srccopies the directory itself into the destination. One of the most common mistakes, and it's silent. - Include/exclude order is rule order. rsync applies the first match it finds.
--exclude='*'always goes last, or it swallows everything else before your includes get a chance. cp --parentsis GNU-only. It doesn't exist on macOS — use the rsync or tar version there instead.
Related questions
How do I copy several extensions at once? Repeat --include: --include='*.jpg' --include='*.png' before --exclude='*'.
Why did rsync copy nothing? Almost always a missing --include='*/' — without it rsync never descends into subdirectories, so it never reaches the files at all.
cp or rsync? rsync once the rules go past one extension, once you want a dry run, or once the copy might get interrupted. cp for a quick, simple, one-off job.
See also
- Find and replace across every file in a project
- Renaming hundreds of files
- Remote tasks in plain English: ssh, scp and rsync
Describe the copy, read the command CliAI writes, then run it. Install it in one line.