同一句 cp -r,落点可能差一层目录;时间戳、符号链接、硬链接会不会保住,全看参数。以下结论均在 WSL 实测。
落点:目标目录已存在时,cp 会套一层
cp -r logs dest # dest 已存在且是目录 -> 结果 dest/logs
cp -r logs dest2 # dest2 不存在 -> dest2 本身即整份拷贝
cp -r -T logs dest3 # -T 关掉"末参数当目录"的特殊处理 -> 结果就是 dest3
GNU 手册口径:最后一个参数是目录且没给 -T 时,cp 按源文件的名字把每个源文件拷进该目录 [1]。所以同一个 cp -r logs dest,dest 已存在会多一层,不存在则 dest 就是拷贝本身;实测三种落点如上。源的尾部斜杠(cp -r logs/ dest)不改变 cp 的落点,实测仍是 dest/logs。不带 -R 直接拷目录会被拒绝,报 -r not specified; omitting directory 且以非零状态退出(实测)[2]。
尾部斜杠:对 cp 没作用,对 rsync 才是语义
rsync 手册原话:源目录带尾部斜杠表示 “copy the contents of this directory”,即内容直接进目标目录,不再多建一层 [3]。
rsync -a logs r1 # -> r1/logs(连目录名一起拷)
rsync -a logs/ r2 # -> 内容直接进 r2,不多一层
实测:r1 里出现 r1/logs,r2 里只有内容。rsync -a 是 -rlptgoD 的速写 [3],实测它把符号链接保留为链接、mtime 保留,但默认不保硬链接——要加 -H。写脚本时别把 rsync 的斜杠习惯套在 cp 上。
属性:默认保 mode,时间戳要 -p;防覆盖用 -n 或 –update=none
stat -c '%a %y' logs/a.txt # 先造基准:chmod 640 + touch -d 2020-01-01
cp logs/a.txt out1.txt # 实测:mode 保住,mtime 变拷贝时刻
cp -p logs/a.txt out2.txt # -p = --preserve=mode,ownership,timestamps [cp(1) 手册页]
cp -n logs/a.txt out2.txt # 已存在则跳过;实测内容未被覆盖,退出码仍为 0
实测 plain cp 只保 mode,时间戳刷新;-p 三项全保。ownership 只在”可能”时保留——普通用户拷别人的文件会变成自己的,这条是手册口径 [2],WSL 里以 root 跑测不出差别。cp -n 跳过后也返回 0;GNU 手册说 -n 因退出码与其他平台不一致已被弃用,建议改用 –update=none [1]——WSL 运行时打印的警告原话就是这句。
符号链接与硬链接:单文件默认解引用,-a/-d 才保住
GNU 手册:cp 只在非递归拷贝或用了 –link 时跟随符号链接,-R/-r 递归时把符号链接照原样保留 [1]。实测吻合:单文件 cp link1 flat.txt 落地成普通文件,cp -d link1 keep1.txt 仍是链接;递归拷目录时链接保留。但硬链接关系默认会断——实测 plain cp -r 后硬链接数从 2 变 1。
cp tree out1 # 符号链接保留;硬链接关系断(link count=1)
cp -a tree out2 # -a = -dR --preserve=all [cp(1) 手册页],link count=2
cp -d link1 keep1.txt # -d = --no-dereference --preserve=links [cp(1) 手册页]
可移植性提醒:手册说单用 -R 拷符号链接不够可移植——POSIX 允许实现默认解引用,要可移植需配合 -P [1](这条仅文档口径,实测只在 GNU 版上验证)。
整目录一次性搬运:tar 管道保结构
mkdir destT
tar cf - -C /srv logs | (cd destT && tar xf -)
diff -r /srv/logs destT/logs # 实测逐字节一致
tar 的归档参数用 - 即走标准输出 [4],管道两头就是源和目标。实测管道后:符号链接仍是链接、硬链接数仍是 2、diff -r 一致——这是 plain cp -r 做不到的。但跨文件系统(U 盘、exFAT 之类不支持硬链接或权限位)要按目标文件系统能力单独验证,WSL 里只在同一 ext4 上测过。
回滚、排错速查与局限
回滚依据:cp 没有撤销。安全做法是先拷到临时目录、diff -r 核对后再动原目录;或加 -i 逐个确认、用 -n/--update=none 防止覆盖已有文件 [2]。
| 现象 | 原因 | 处理 | |—|—|—| | 目标里多了一层目录 | 目标是已存在的目录,cp 按源名往里拷 [1] | 用完整目标路径或 -T | | 硬链接数从 2 变 1 | plain cp -r 不保 links | -a 或 –preserve=links | | 拷完时间戳变新 | 默认不保 timestamps | 用 -p | | 单文件符号链接拷成实体文件 | cp 非递归时默认解引用 [1] | 用 -d | | 同名文件被覆盖 | cp 默认覆盖 | -i 确认或 -n/–update=none | | 报 are the same file / into itself | 源与目标是同一文件或目录 | 换目标路径;错误返回非零(手册定义零成功、非零失败 [1],三个场景实测均非零) |
四个常见问题:cp 的尾部斜杠有用吗——没有,rsync 才有语义;cp -r 与 cp -a 怎么选——要保时间戳、权限、链接就 -a,plain -r 会丢硬链接和 mtime;rsync -a 保硬链接吗——默认不保,要 -H [3];稀疏文件要不要担心——cp 默认 –sparse=auto,输入稀疏则尽量输出稀疏 [1](仅文档口径)。
状态:草稿待审。实测环境 WSL2 Ubuntu 24.04.4:coreutils 9.4(noble 包页 9.4-3ubuntu6 [5])、GNU tar 1.35、rsync 3.2.7,日期 2026-10-26。已实测:三种落点与 -T、cp 尾部斜杠无作用、rsync 尾部斜杠语义、默认保 mode 不保 mtime、-p、cp -n 跳过且返回 0、单符号链接解引用与 -d、-R 保留符号链接、硬链接 plain -r 断而 -a 保住、tar 管道保结构、三类错误非零退出、无 -R 拒拷目录。仅文档核查尚未执行:–sparse 默认口径、POSIX -R 解引用口径、非 root 时 ownership、跨文件系统与 RPM 平台行为。
参考资料
- [1] GNU Coreutils manual – cp invocation(GNU coreutils 手册)
- [2] cp(1) – User Commands – Debian man page(cp(1) 手册页)
- [3] rsync(1) manual – rsync.samba.org(rsync 手册)
- [4] tar(1) – Linux manual page(tar(1) 手册页)
- [5] coreutils — Ubuntu (noble) packages(Ubuntu 包页)
文中主要命令已在 WSL Ubuntu(24.04)实测,个别条目(未装工具/安装类命令)仅核对官方文档,未实际执行。