终于把 cp 复制目录时的各种情况做了个总结。目录也是一种特殊的文件,但在复制时仍然有一些容易混淆的地方,尤其是源路径末尾的 /、通配符 *、以及目标目录是否已经存在。
下面用 [^] 表示空格。假设 /a 目录下有文件或子目录 1、2、3。
Table of Contents
cp[^]-R[^]/a/*[^]/b
等同于:
cp -R /a/* /b/
这会把 /a 目录下匹配 * 的内容复制到 /b 目录下。也就是说,复制的是 /a 里面的各个文件和子目录,而不是 /a 目录本身。
需要注意两点:
*由 shell 展开,默认不会匹配隐藏文件,例如.gitignore、.env、.config等。- 如果
/a下面有子目录,要递归复制它们,需要加-R或-r。在现代 GNU coreutils 里,-R和-r通常等价;如果关心可移植性,优先使用 POSIX 规定的-R。
如果想连隐藏文件也一起复制,可以考虑使用:
cp -R /a/. /b/
这里的 /a/. 表示复制 /a 的内容到 /b,同时包括普通文件、隐藏文件和子目录。
cp[^]-R[^]/a[^]/b/c
这种写法要分两种情况讨论:目标路径 /b/c 是否已经存在。
如果 /b/c 不存在
命令会创建 /b/c,并把 /a 中的所有文件和子目录复制到 /b/c 中。效果相当于把 /a 克隆为一个新的目录 /b/c。
cp -R /a /b/c
复制后的结构类似:
/b/c/1
/b/c/2
/b/c/3
如果 /b/c 已经存在
命令会把 /a 目录本身复制到 /b/c 中。复制成功后,目录结构为:
/b/c/a/1
/b/c/a/2
/b/c/a/3
也就是说,目标目录已存在时,cp -R /a /b/c 的结果不是把 /a 的内容平铺到 /b/c,而是在 /b/c 下面再创建一个 a 目录。
cp[^]-R[^]/a/[^]/b
在 GNU/Linux 常见的 cp 实现中,源路径末尾的 / 通常不会改变复制目录时的主要行为。也就是说:
cp -R /a/ /b
通常与下面这条命令效果相近:
cp -R /a /b
如果 /b 已存在,复制后的结果一般是:
/b/a/1
/b/a/2
/b/a/3
如果没有加 -R 或 -r,才会出现类似下面的错误:
cp: omitting directory '/a/'
cp[^]-R[^]/a/[^]/b/
目标路径末尾的 / 表示目标应该是一个目录。因此:
cp -R /a/ /b/
在 /b 已经存在时,通常会把 /a 目录复制到 /b 下面,结果为:
/b/a/1
/b/a/2
/b/a/3
如果 /b 不存在,不同系统和版本的报错信息可能略有差异。实际使用前,可以用下面的命令在本机验证行为:
mkdir -p /tmp/cp-test/a
printf 'one\n' > /tmp/cp-test/a/1
printf 'two\n' > /tmp/cp-test/a/2
printf 'three\n' > /tmp/cp-test/a/3
mkdir -p /tmp/cp-test/b
cp -R /tmp/cp-test/a/ /tmp/cp-test/b/
find /tmp/cp-test -maxdepth 3 -type f | sort
小结
常用结论如下:
cp -R /a/* /b/
复制 /a 里面被 * 匹配到的内容到 /b,默认不包括隐藏文件。
cp -R /a/. /b/
复制 /a 里面的全部内容到 /b,包括隐藏文件。
cp -R /a /b/c
如果 /b/c 不存在,把 /a 克隆成 /b/c;如果 /b/c 已存在,把 /a 复制成 /b/c/a。
cp -R /a/ /b/
在常见 GNU/Linux 环境中,源路径末尾的 / 通常不会让 cp 只复制目录内容;要复制目录内容而不是目录本身,更明确的写法是 cp -R /a/. /b/。
