Короткий ответ: cp --parents и rsync копируют одни и те же файлы, но строят разные деревья в месте назначения. find src -name '*.txt' -exec cp --parents {} dst/ \; кладёт файлы в dst/src/a/b/file; версия с rsync и include/exclude кладёт те же файлы в dst/a/b/file — без сегмента src. Выбрали не тот способ — получили лишний уровень вложенности, о котором никто не предупредил.
1. Как скопировать файлы по шаблону, сохранив структуру папок?
$ clai скопируй все txt из src в dst, сохранив структуру папок→ find src -name '*.txt' -exec cp --parents {} dst/ \;
--parents восстанавливает в назначении весь путь до файла. Результат — dst/src/a/b/file: сам src оказывается внутри назначения, потому что он часть пути, переданного find. Это и есть сюрприз, о котором вся статья.
2. Почему rsync строит другое дерево для той же задачи?
$ clai скопируй только txt файлы из src в dst2, сохранив вложенность→ rsync -a --include='*/' --include='*.txt' --exclude='*' src/ dst2/
Те же файлы, другой результат: dst2/a/b/file, без уровня src. Слеш в конце src/ означает «содержимое каталога», а не сам каталог. Порядок правил тоже важен: сначала разрешить обход всех каталогов через --include='*/', потом нужные файлы, и только потом исключить остальное. Переставьте --exclude='*' вперёд — не скопируется ничего.
3. Как проверить, что скопирует rsync, до запуска?
$ clai покажи, что скопируется, но ничего не копируй→ rsync -avn --include='*/' --include='*.txt' --exclude='*' src/ dst2/
-n — холостой прогон. С непростыми правилами include и exclude пропустить его значит узнать результат постфактум, а не заранее. Запускайте -n первым, всегда.
4. Как скопировать файлы на удалённый сервер с той же структурой?
$ clai перенеси все txt с сохранением структуры на удалённый сервер→ find src -name '*.txt' -print0 | tar --null -cf - -T - | ssh user@host 'tar -xf - -C /dest'
tar упаковывает файлы в поток и распаковывает на другой стороне, структура сохраняется как есть. -print0 и --null защищают от пробелов в именах. Один проход по сети вместо тысячи отдельных копирований.
5. Как копировать, не перезаписывая более новые файлы в назначении?
$ clai скопируй файлы, но не трогай те, что в назначении новее→ rsync -au src/ dst/
-u пропускает файлы, у которых время изменения в назначении новее. Нужно, когда синхронизация идёт в обе стороны или когда в назначении уже есть правки, которые не стоит затирать.
6. Как воссоздать только дерево каталогов, без файлов?
$ clai создай в dst такое же дерево папок, как в src, без файлов→ rsync -a --include='*/' --exclude='*' src/ dst/
Те же правила минус строка, пропускающая файлы. Полезно, когда нужно подготовить дерево под вывод — например, перед пакетной конвертацией картинок.
Грабли
- Слеш в конце источника у rsync меняет всё.
src/копирует содержимое,srcкопирует сам каталог внутрь назначения. Одна из самых частых ошибок, и она молчаливая. - Порядок include и exclude — это порядок правил. rsync применяет первое подошедшее правило.
--exclude='*'всегда идёт последним, иначе он съест всё остальное раньше, чем сработают include. cp --parentsесть только в GNU. На macOS его нет — там нужен вариант с rsync или tar.
Похожие вопросы
Как скопировать сразу несколько расширений? Повторите --include: --include='*.jpg' --include='*.png' перед --exclude='*'.
Почему rsync ничего не скопировал? Почти всегда забыт --include='*/': без него rsync не заходит в подкаталоги и до файлов просто не доходит.
cp или rsync? rsync — если правила сложнее одного расширения, если нужен холостой прогон или если копирование может прерваться. cp — для простых разовых случаев.
Читайте также
- Найти и заменить во всех файлах проекта
- Переименовать сотни файлов
- Удалённые задачи простыми словами: ssh, scp и rsync
Опишите копирование, прочитайте команду, которую напишет CliAI, и запустите её. Установка одной строкой.