简短回答:cp --parents 和 rsync 复制的是同一批文件,但在目标位置构建出完全不同的目录树。find src -name '*.txt' -exec cp --parents {} dst/ \; 会把文件放到 dst/src/a/b/file;用 include/exclude 规则的 rsync 版本会把同样的文件放到 dst/a/b/file——没有 src 这一层。选错方法,就会多出一层没人提醒过你的嵌套。
1. 如何按模式复制文件并保留目录结构?
$ clai 把 src 下所有 txt 文件复制到 dst,保留目录结构→ find src -name '*.txt' -exec cp --parents {} dst/ \;
--parents 会在目标位置重建通往该文件的完整路径。结果是 dst/src/a/b/file:src 本身也进了目标目录,因为它是你传给 find 的路径的一部分。这正是本文要讲的意外之处。
2. 为什么同样的任务,rsync 建出的目标目录树不一样?
$ clai 只把 src 下的 txt 文件复制到 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 是空跑一次。规则不简单的时候,跳过 -n 就意味着事后才知道结果,而不是提前知道。永远先跑一次 -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:在 --exclude='*' 之前加上 --include='*.jpg' --include='*.png'。
为什么 rsync 什么都没复制? 几乎总是漏了 --include='*/':没有它,rsync 永远不会进入子目录,自然也就碰不到文件。
该用 cp 还是 rsync? 规则超过一种扩展名、需要先空跑一次、或者复制过程可能被打断时,用 rsync。简单的一次性任务,用 cp 就够了。
另请参阅
描述你想要的复制方式,看看 CliAI 生成的命令,再运行它。一行命令安装。