Skip to content

cp

cpcopy)是 GNU coreutils 中选项最丰富的命令。表面只是"复制粘贴",内里却藏着符号链接跟随策略、稀疏文件处理、写时复制克隆、尾部斜杠行为分歧、以及 POSIX 与 GNU 之间的微妙差异——每一个坑都足以让一个看似无害的备份脚本悄悄出错。

速查卡片

需求命令
复制文件cp src dst
复制到目录cp file1 file2 /target/dir/
递归复制目录cp -r src_dir/ dst_dir/
归档复制(保留全部元数据)cp -a src/ dst/
覆盖前确认cp -i src dst
不覆盖已存在文件cp -n src dst
仅源更新时才复制cp -u src dst
覆盖前备份旧文件cp -b src dst(旧文件 → dst~)
详细输出cp -v src dst
复制符号链接本身(不跟随)cp -P link dstcp -a
创建硬链接而非复制cp -l src dst
创建符号链接而非复制cp -s src dst
CoW 轻量复制(Btrfs/XFS)cp --reflink=always src dst
保留源路径层级cp --parents a/b/c.txt /tmp/

概述

cp 是文件系统操作的核心命令,属于 GNU coreutils。POSIX 定义了基础行为(-R/-i/-f/-p),GNU 在此基础上扩展了大量选项——从 --reflink 写时复制到 --sparse 稀疏文件处理。理解这些选项之间的交互是正确使用 cp 的关键。

命令签名

bash
# 形式一:单文件复制
cp [OPTION]... [-T] SOURCE DEST

# 形式二:多文件复制到目录
cp [OPTION]... SOURCE... DIRECTORY

# 形式三:-t 指定目标目录(GNU 扩展)
cp [OPTION]... -t DIRECTORY SOURCE...
  • -T:强制将 DEST 视为普通文件(即使它是已存在目录也不把源复制进去)
  • -t:将目标目录放到前面,适合配合 find ... -execxargs 批量操作

基础用法

示例一:最简——单文件复制

bash
$ cp file.txt /tmp/file.txt
$ cp file.txt /tmp/            # 目标为目录时自动放入,保留原文件名
$ cp file.txt /tmp/newname.txt # 同时改名

示例二:多文件复制 + 递归目录

bash
$ cp a.txt b.txt c.txt /tmp/          # 多文件复制到目录
$ cp -r myproject/ /backup/           # 递归复制整个目录树
$ cp -r dir1 dir2 dir3 /dest/         # 多个目录一起复制

示例三:归档模式——最常用的复制方式

bash
$ cp -a /etc/nginx /backup/

-a 等价于 -dR --preserve=all,展开即:

子选项含义
-d不跟随符号链接 + 保留链接关系
-R递归复制目录
--preserve=mode保留权限位和 ACL
--preserve=ownership保留所有者和属组(需 root 权限)
--preserve=timestamps保留修改时间和访问时间
--preserve=links保留硬链接关系
--preserve=context保留 SELinux 安全上下文
--preserve=xattr保留扩展属性

示例四:尾部斜杠——最隐蔽的坑

bash
$ tree src/
src/
├── a.txt
└── sub/
    └── b.txt

# /tmp/dst 不存在 → 创建 dst,内容为 src 的副本
$ cp -r src /tmp/dst
$ tree /tmp/dst/
/tmp/dst/
├── a.txt
└── sub/
    └── b.txt

# /tmp/dst 已存在 → 复制 src **本身**到 dst 内
$ cp -r src /tmp/dst       # → /tmp/dst/src/a.txt ...

# 尾部 / → 始终保持"复制内容"语义
$ cp -r src/ /tmp/dst      # → /tmp/dst/a.txt ...(无论 dst 是否已存在)

核心规则:cp -r src /tmp/dst 的行为取决于 /tmp/dst 是否存在——不存在则创建为副本,存在则将 src 放入其中。而 cp -r src/ /tmp/dst 始终复制内容。脚本中务必显式明确意图。

选项详解

覆盖控制

选项行为
(默认)静默覆盖,不提示
-i / --interactive覆盖前提示确认(y/n)
-n / --no-clobber静默跳过已存在文件(已废弃,建议用 --update=none
-u / --update仅当源比目标新、或目标不存在时才复制
--update=none不覆盖,静默跳过
--update=none-fail不覆盖,跳过时输出诊断并返回非零退出码
--update=older-u 的显式等价写法
-b / --backup覆盖前备份旧文件,后缀默认 ~
-f / --force目标不可打开时先 unlink() 再重试
--remove-destination打开目标之前先删除(比 -f 更激进)

⚠️ POSIX -f 仅"静默覆盖不提示";GNU -f 额外增加了"失败时删除重试"的逻辑。如果你在同时兼容 BSD 的脚本里用 -f,小心行为差异。

-f vs --remove-destination 的区别

bash
# touch 创建只读文件
$ touch /tmp/readonly && chmod 444 /tmp/readonly

$ cp file.txt /tmp/readonly -f
# → 打开失败后 unlink 重试:成功

$ cp file.txt /tmp/readonly --remove-destination
# → 先 unlink 再创建:也成功,但语义不同

# 关键差异:目标为符号链接时
$ ln -s /important/data /tmp/link
$ cp -f file.txt /tmp/link        # → 覆盖 /important/data(跟随链接)
$ cp --remove-destination file.txt /tmp/link  # → 删除链接本身,创建新文件

符号链接处理

GNU cp 提供三级符号链接控制 + 两种"不复制数据"模式:

选项对符号链接的行为
-P / --no-dereference不跟随——复制链接本身(默认
-L / --dereference始终跟随——复制链接指向的文件内容
-H仅命令行参数中跟随——递归时子目录内的链接仍不跟随
-l / --link不复制数据——创建指向相同 inode 的硬链接
-s / --symbolic-link不复制数据——创建符号链接
bash
$ ln -s /etc/hostname link_to_hostname

$ cp -P link_to_hostname /tmp/    # 复制链接本身
$ ls -l /tmp/link_to_hostname
lrwxrwxrwx ... /tmp/link_to_hostname -> /etc/hostname

$ cp -L link_to_hostname /tmp/    # 复制目标文件内容
$ cat /tmp/link_to_hostname
myhost

$ cp -a dir/ /backup/             # 归档模式:不跟随(= -P + preserve links)

高级选项

--preserve —— 精细控制元数据

bash
--preserve=mode           # 权限位(含 ACL)
--preserve=ownership      # 用户和组(非 root 时组可能丢失)
--preserve=timestamps     # 修改/访问时间
--preserve=links           # 硬链接关系
--preserve=context         # SELinux 安全上下文
--preserve=xattr           # 扩展属性
--preserve=all             # 以上全部(= -a 的 --preserve 部分)

# 选择性保留
cp --preserve=mode,timestamps src dst

在 Btrfs、XFS、OCFS2 等现代文件系统上,--reflink 创建共享数据块的克隆——不占用额外空间,修改时按需复制:

bash
cp --reflink=always huge_file.iso snapshot.iso   # 秒级完成,零额外空间
cp --reflink=auto huge_file.iso snapshot.iso     # 文件系统不支持时回退到普通复制
cp --reflink=never huge_file.iso snapshot.iso    # 强制普通复制

--sparse —— 稀疏文件处理

稀疏文件包含大量连续的零字节空洞,--sparse 控制如何处理它们:

bash
cp --sparse=auto disk.img /tmp/     # 启发式检测,遇洞留洞(默认)
cp --sparse=always disk.img /tmp/   # 任何足够长的零序列都做成洞
cp --sparse=never disk.img /tmp/    # 逐字节写入,洞被填实

--parents —— 保留路径层级

bash
$ cp --parents src/a/b/c.txt /tmp/
$ tree /tmp/
/tmp/
└── src/
    └── a/
        └── b/
            └── c.txt          # 自动创建中间目录

-x / --one-file-system

bash
cp -ax /home /backup/home      # 不跨越文件系统边界
# /home/ 下挂载的其他分区内容不会被复制

覆盖控制选项的相互覆盖规则

多个覆盖相关选项同时出现时,后出现的覆盖前面的:

bash
cp -i -n src dst    # -n 覆盖 -i,静默跳过
cp -n -i src dst    # -i 覆盖 -n,覆盖前提示
cp -f -n src dst    # -n 覆盖 -f,-f 被忽略
cp -u -n src dst    # -n 覆盖 -u

常用命令组合

bash
# 批量复制 + 进度监控
find . -name '*.txt' -print0 | xargs -0 cp -t /dest/ -v

# 仅复制变化的文件(增量式备份)
cp -auv /source/ /backup/

# 完整备份(保留一切)
cp -a /etc /backup/etc-$(date +%Y%m%d)

# 安全复制(不覆盖已有)
cp -n /source/*.conf /etc/

# CoW 快照(btrfs/xfs)
cp --reflink=always /data/vm.qcow2 /data/snapshot.qcow2

# 保留路径层级提取
find src -name '*.h' -exec cp --parents {} /tmp/headers/ \;

脚本中的 cp

安全复制原则

bash
# ❌ 危险:覆盖不提示
cp /tmp/build/output.bin /deploy/

# ✅ 安全:仅源更新时复制
cp -u /tmp/build/output.bin /deploy/

# ✅ 安全:不覆盖已有配置
cp -n /tmp/new.conf /etc/app/config.conf

# ✅ 安全:覆盖前备份
cp -b /tmp/new.conf /etc/app/config.conf    # 旧文件 → config.conf~

常见脚本模式

bash
# 精确控制尾部斜杠——始终显式指定目标文件名或目录
cp -r src/ /backup/src/        # 明确:复制内容到 /backup/src/
cp -r src  /backup/            # 明确:复制 src 目录本身到 /backup/

# 批量复制优先用 -t(目标在前)
find . -type f -name '*.log' -exec cp -t /archive/logs/ {} +

# 条件复制:仅目标不存在时
[ -f /etc/app.conf ] || cp /opt/default.conf /etc/app.conf

# 复制后验证(关键数据)
cp important.dat /backup/ && sha256sum -c <<< "$(sha256sum important.dat)"

常见报错

错误信息原因解决
cp: omitting directory 'src/'复制目录没加 -r-r-a
cp: 'src' and 'dst' are the same file源和目标是同一文件(如 cp file .检查路径,确认目标不同于源
cp: will not overwrite just-created同一次批量复制中尝试用同名文件覆盖刚创建的文件源中有重名文件,用 rsync 或检查源结构
cp: cannot create regular file: Permission denied目标目录无写权限,或文件存在且只读chmod 或使用 --remove-destination
cp: not writing through dangling symlink目标是悬挂的符号链接删除悬挂链接或指向有效路径
cp: cannot stat 'file': No such file or directory源文件不存在检查路径拼写
cp: failed to preserve ownership非 root 用户尝试保留文件所有者去掉 --preserve=ownership 或用 sudo

注意事项

  • cp 不保证原子性——复制大文件时如果中断,目标是不完整文件。mv(同文件系统内)是原子的
  • 默认不保留 ACL 和 xattr——仅 -p 不够,需 --preserve=xattr 或直接用 -a
  • cp 默认跟随符号链接 指的是目标操作,但 -P(默认)控制的是源——两个维度不要混淆
  • 默认不保留稀疏文件的"空洞"特性——除非源文件被成功检测为稀疏文件(--sparse=auto 的启发式算法),空洞可能在复制过程中"实化"
  • --backup 的备份文件可能累积——多次 cp -b 会创建 file~file.~1~ 等;需定期清理或使用 --backup=numbered

常见误区

❌ 误区✅ 正解
"cp -rcp -R 有区别"GNU cp 中完全一致;POSIX 中 -r 行为由实现定义,-R 有明确规定——习惯用 -R
"cp -f 就是强制覆盖"POSIX 下仅静默覆盖;GNU 下额外增加 unlink 重试逻辑;最激进的选项是 --remove-destination
"cp -a src/ dst/cp -a src dst/ 一样"尾部斜杠决定复制的是内容还是目录本身(见示例四)
"cp 保留所有文件元数据"默认不保留时间戳和权限;-p 保留部分;完整保留需 -a--preserve=all
"源和目标同名自动跳过"默认静默覆盖——需显式加 -n-i
"cp -l 复制了数据"-l 仅创建硬链接,不复制任何数据块

cp vs rsync 场景选择

场景推荐命令原因
本地简单复制cp -a简单直接
远程复制rsync -avzcp 不支持远程
增量备份rsync -av --deletecp -u 无法处理删除/差异传输
CoW 快照cp --reflink=alwaysrsync 不理解 CoW
需要排除模式rsync --excludecp 无原生排除
大文件断点续传rsync --partialcp 中断后需从头来
find + 批量复制cp -t + xargs管道友好

版本与规范信息

事件说明
POSIX 纳入Issue 2(基础 cp
GNU coreutils 当前9.11(2026-04)
CoW/reflink 支持Linux 内核 4.5+ / Btrfs、XFS、OCFS2
-n 状态已废弃(deprecated),建议用 --update=none

macOS 特别说明

  • macOS 的 cp 来自 BSD,选项集远小于 GNU 版本
  • BSD cp 没有 -a--reflink--parents-t-T 等 GNU 扩展
  • BSD cp -r 行为可能不同于 GNU cp -r(不处理特殊文件)
  • BSD cp 对尾部斜杠的处理与 GNU 可能不同
  • macOS 文件系统(APFS)支持 CoW 克隆,但需使用 cp -c(BSD 标志,非 --reflink

来源

来源链接
GNU coreutils cp man pagehttps://man7.org/linux/man-pages/man1/cp.1.html
GNU coreutils 官方文档https://www.gnu.org/software/coreutils/cp
POSIX cp 规范https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cp.html
Linuxize cp 实战指南https://linuxize.com/post/cp-command-in-linux/
Baeldung: cp 警告排查https://www.baeldung.com/linux/find-cp-avoid-warnings
整理时间2026-07-14