cp 命令详解与参数实测(Ubuntu 22.04)
GNU cp 参数实测:默认复制、-r 递归、-p/-a 属性保留、-u/-i/-n/-f 覆盖控制、-b 备份、-l 硬链与 -s 软链复制,Ubuntu 22.04 实测。
cp 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 GNU cp 的常用参数——默认复制、
-r递归、-p/-a属性保留(stat 验证)、-u更新复制、-i/-n/-f覆盖控制、-b备份、-l硬链复制、-s符号链接复制,以及-d/-t/-T目标形态控制,全部命令与回显已在 Ubuntu 22.04(GNU coreutils 8.32)实测通过。
一、环境信息
本篇所有命令与回显均来自同一台 Ubuntu 22.04 服务器的实测,演示数据统一放在 /root/demo/cp/ 下,读者在任意 Ubuntu 22.04 机器上按相同步骤均可复现。
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- cp 版本:GNU coreutils 8.32(
cp --version实测) - cp 属于 coreutils 基础工具集,Ubuntu 22.04 默认预装,无需额外安装
- 演示目录:
/root/demo/cp/ - 测试数据:
src/file.txt(权限 640、mtime 设为 2025-01-01)、src/sub/a.txt、指向src/file.txt的软链src/link.lnk
二、命令概览
2.1 语法与测试数据准备
cp 的完整语法共三种形态(摘自 cp --help 的 Usage 行,原文为 cp [OPTION]... [-T] SOURCE DEST 等三行):单个源复制到目标、多个源复制进目录、以及用 -t 显式指定目标目录。职责很纯粹:把 SOURCE 复制为 DEST,或把多个 SOURCE 复制进 DIRECTORY。
先准备一套演示数据。file.txt 被特意设置成权限 640、mtime 2025-01-01,供后面 -p/-a 的属性保留验证;link.lnk 是指向 file.txt 的软链,供 -r/-a/-d 的链接行为验证:
mkdir -p /root/demo/cp/src/sub
cat > /root/demo/cp/src/file.txt <<'EOF'
line1
line2
EOF
cat > /root/demo/cp/src/sub/a.txt <<'EOF'
AAA
EOF
touch -d '2025-01-01 08:00:00' /root/demo/cp/src/file.txt
chmod 640 /root/demo/cp/src/file.txt
ln -s /root/demo/cp/src/file.txt /root/demo/cp/src/link.lnk
验证数据就绪:
$ ls -lR /root/demo/cp
/root/demo/cp:
total 4
drwxr-xr-x 3 root root 4096 Oct 15 06:35 src
/root/demo/cp/src:
total 8
-rw-r----- 1 root root 12 Jan 1 2025 file.txt
lrwxrwxrwx 1 root root 26 Oct 15 06:35 link.lnk -> /root/demo/cp/src/file.txt
drwxr-xr-x 2 root root 4096 Oct 15 06:35 sub
/root/demo/cp/src/sub:
total 4
-rw-r--r-- 1 root root 4 Oct 15 06:35 a.txt
file.txt 权限 640、mtime 为 2025 年,link.lnk 是软链,三项特征数据都符合预期,可以作为后续参数实测的基准。
2.2 --help 全量回显
cp --help 输出全部参数清单(实测原样保留,可作为参数速查):
Usage: cp [OPTION]... [-T] SOURCE DEST
or: cp [OPTION]... SOURCE... DIRECTORY
or: cp [OPTION]... -t DIRECTORY SOURCE...
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
Mandatory arguments to long options are mandatory for short options too.
-a, --archive same as -dR --preserve=all
--attributes-only don't copy the file data, just the attributes
--backup[=CONTROL] make a backup of each existing destination file
-b like --backup but does not accept an argument
--copy-contents copy contents of special files when recursive
-d same as --no-dereference --preserve=links
-f, --force if an existing destination file cannot be
opened, remove it and try again (this option
is ignored when the -n option is also used)
-i, --interactive prompt before overwrite (overrides a previous -n
option)
-H follow command-line symbolic links in SOURCE
-l, --link hard link files instead of copying
-L, --dereference always follow symbolic links in SOURCE
-n, --no-clobber do not overwrite an existing file (overrides
a previous -i option)
-P, --no-dereference never follow symbolic links in SOURCE
-p same as --preserve=mode,ownership,timestamps
--preserve[=ATTR_LIST] preserve the specified attributes (default:
mode,ownership,timestamps), if possible
additional attributes: context, links, xattr,
all
--no-preserve=ATTR_LIST don't preserve the specified attributes
--parents use full source file name under DIRECTORY
-R, -r, --recursive copy directories recursively
--reflink[=WHEN] control clone/CoW copies. See below
--remove-destination remove each existing destination file before
attempting to open it (contrast with --force)
--sparse=WHEN control creation of sparse files. See below
--strip-trailing-slashes remove any trailing slashes from each SOURCE
argument
-s, --symbolic-link make symbolic links instead of copying
-S, --suffix=SUFFIX override the usual backup suffix
-t, --target-directory=DIRECTORY copy all SOURCE arguments into DIRECTORY
-T, --no-target-directory treat DEST as a normal file
-u, --update copy only when the SOURCE file is newer
than the destination file or when the
destination file is missing
-v, --verbose explain what is being done
-x, --one-file-system stay on this file system
-Z set SELinux security context of destination
file to default type
--context[=CTX] like -Z, or if CTX is specified then set the
SELinux or SMACK security context to CTX
--help display this help and exit
--version output version information and exit
By default, sparse SOURCE files are detected by a crude heuristic and the
corresponding DEST file is made sparse as well. That is the behavior
selected by --sparse=auto. Specify --sparse=always to create a sparse DEST
file whenever the SOURCE file contains a long enough sequence of zero bytes.
Use --sparse=never to inhibit creation of sparse files.
When --reflink[=always] is specified, perform a lightweight copy, where the
data blocks are copied only when modified. If this is not possible the copy
fails, or if --reflink=auto is specified, fall back to a standard copy.
Use --reflink=never to ensure a standard copy is performed.
The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.
The version control method may be selected via the --backup option or through
the VERSION_CONTROL environment variable. Here are the values:
none, off never make backups (even if --backup is given)
numbered, t make numbered backups
existing, nil numbered if numbered backups exist, simple otherwise
simple, never always make simple backups
As a special case, cp makes a backup of SOURCE when the force and backup
options are given and SOURCE and DEST are the same name for an existing,
regular file.
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Report any translation bugs to <https://translationproject.org/team/>
Full documentation <https://www.gnu.org/software/coreutils/cp>
or available locally via: info '(coreutils) cp invocation'
💡 说明:两个覆盖控制参数的互斥关系帮助里写得很清楚——
-i会覆盖之前的-n,-n也会覆盖之前的-i,与 mv 一致的原则是「同时指定多个-i/-f/-n时只有最后一个生效」。备份后缀默认为~,可用-S或SIMPLE_BACKUP_SUFFIX环境变量改变。
2.3 版本确认
$ cp --version | head -1
cp (GNU coreutils) 8.32
三、参数逐项实测
本章逐个参数给出「命令 + 实测回显 + 说明」,所有回显与服务器日志逐字一致;演示文件全部位于 /root/demo/cp/。
3.1 默认行为:复制文件(mtime 会变成复制时刻)
不带参数的 cp 复制单个文件:目标不存在则创建,内容与源一致,但时间戳默认不保留:
$ cp /root/demo/cp/src/file.txt /root/demo/cp/copy.txt
$ cat /root/demo/cp/copy.txt
line1
line2
$ ls -l /root/demo/cp/src/file.txt /root/demo/cp/copy.txt
-rw-r----- 1 root root 12 Oct 15 06:35 /root/demo/cp/copy.txt
-rw-r----- 1 root root 12 Jan 1 2025 /root/demo/cp/src/file.txt
两份文件内容相同、权限相同,但 copy.txt 的 mtime 是复制发生的时刻(Oct 15 06:35),源文件的 2025-01-01 没有被带过来——需要保留属性时要显式加 -p 或 -a(见 3.3、3.4)。
💡 说明:cp 的默认口径是"内容等价、属性不保证"——mtime 换成复制时刻,属主变成执行 cp 的用户。这是增量同步场景最常见的坑:部署脚本里用
cp后服务按新 mtime 重载,或备份后文件时间全部失效。要"看起来像原文件"就用-p(mtime+权限)或-a(归档级全保留)。
复制到不存在的目录会直接失败:
$ cp /root/demo/cp/src/file.txt /root/demo/cp/nodir/copy.txt; echo exit=$?
cp: cannot create regular file '/root/demo/cp/nodir/copy.txt': No such file or directory
exit=1
父目录 nodir 不存在,cp 不会自动补建目录,退出码 1;先 mkdir -p 再复制即可。
💡 技术拓展:把一份文件"变成"另一份可访问的名字,不止「复制」一条路,四种方式横向对比——
方式 数据冗余 inode 原文件变化影响 适用场景 复制(默认 / -r)完整数据副本,占双倍空间 新 inode 互不影响 常规备份、发布产物 硬链复制( -l)零冗余,只加一个目录项 与源相同 同步变化(本就是同一份数据) 免拷贝引用、省磁盘 符号链接复制( -s)仅存路径串(几十字节) 新 inode(链接自身) 原文件删除/改名后悬空 快捷入口、版本切换 归档复制( -a)完整冗余且保留全部属性 新 inode 互不影响 目录树备份、数据迁移 本文以「默认复制」为主线演示,硬链、软链、归档三种形态分别在 3.11、3.12、3.4 实测。
3.2 -r:递归复制目录(目录内软链按链接原样保留)
cp 对目录默认拒绝处理,必须加 -r 才能递归复制:
$ cp /root/demo/cp/src /root/demo/cp/dirless; echo exit=$?
cp: -r not specified; omitting directory '/root/demo/cp/src'
exit=1
$ cp -r /root/demo/cp/src /root/demo/cp/rcopy && ls -lR /root/demo/cp/rcopy
/root/demo/cp/rcopy:
total 8
-rw-r----- 1 root root 12 Oct 15 06:35 file.txt
lrwxrwxrwx 1 root root 26 Oct 15 06:35 link.lnk -> /root/demo/cp/src/file.txt
drwxr-xr-x 2 root root 4096 Oct 15 06:35 sub
/root/demo/cp/rcopy/sub:
total 4
-rw-r--r-- 1 root root 4 Oct 15 06:35 a.txt
-r 把整个目录树原样复制了一份:子目录 sub、其中的 a.txt 都在。注意一个实测观察:目录内的软链 link.lnk 仍以软链形态保留(lrwxrwxrwx、指向不变),并没有被展开成实体文件——cp 递归复制时对目录树内遇到的软链按链接本身复制。
3.3 -p:保留 mode 与 mtime(stat 验证)
-p 等价于 --preserve=mode,ownership,timestamps(帮助原文),复制后属性与源一致:
$ cp -p /root/demo/cp/src/file.txt /root/demo/cp/p.txt
$ stat -c 'mode=%a mtime=%y name=%n' /root/demo/cp/src/file.txt /root/demo/cp/p.txt
mode=640 mtime=2025-01-01 08:00:00.000000000 +0000 name=/root/demo/cp/src/file.txt
mode=640 mtime=2025-01-01 08:00:00.000000000 +0000 name=/root/demo/cp/p.txt
源与副本的 mode(640)和 mtime(2025-01-01)完全一致。作为对照,3.2 中不带 -p 的递归副本 rcopy 的 mtime 是复制时刻:
$ stat -c 'mode=%a mtime=%y name=%n' /root/demo/cp/rcopy/file.txt
mode=640 mtime=2026-10-15 06:35:40.837402536 +0000 name=/root/demo/cp/rcopy/file.txt
一加一不加,差异一目了然:凡是对「文件时间」有语义的场景(备份、增量同步的比对依据),复制时都要带 -p 或 -a。
3.4 -a:归档复制(等价 -dR --preserve=all)
-a(--archive)是「归档式复制」,帮助原文注明等价于 -dR --preserve=all:递归 + 不解引用链接 + 尽量保留全部属性(mode、ownership、timestamps,还含 links、xattr 等)。复制整棵目录树:
$ cp -a /root/demo/cp/src /root/demo/cp/acopy && ls -lR /root/demo/cp/acopy
/root/demo/cp/acopy:
total 8
-rw-r----- 1 root root 12 Jan 1 2025 file.txt
lrwxrwxrwx 1 root root 26 Oct 15 06:35 link.lnk -> /root/demo/cp/src/file.txt
drwxr-xr-x 2 root root 4096 Oct 15 06:35 sub
/root/demo/cp/acopy/sub:
total 4
-rw-r--r-- 1 root root 4 Oct 15 06:35 a.txt
$ stat -c 'mode=%a mtime=%y name=%n' /root/demo/cp/acopy/file.txt
mode=640 mtime=2025-01-01 08:00:00.000000000 +0000 name=/root/demo/cp/acopy/file.txt
ls -l 里 acopy/file.txt 显示的时间直接是 Jan 1 2025(与源一致),stat 确认 mode 640、mtime 2025-01-01 原样保留;软链也保持链接形态。-a 与 -r 的关键差异就是属性保留:备份、迁移目录树首选 -a。
3.5 -u:仅当源比目标新或目标缺失时复制
-u(--update)实现"只更新"语义。目标比源旧(或不存在)时复制;目标比源新时跳过:
$ printf 'newer\n' > /root/demo/cp/u-src.txt
$ printf 'old\n' > /root/demo/cp/u-dst.txt && touch -d '2025-01-01 08:00:00' /root/demo/cp/u-dst.txt
$ cp -u /root/demo/cp/u-src.txt /root/demo/cp/u-dst.txt && cat /root/demo/cp/u-dst.txt
newer
$ touch -d '2026-10-15 23:59:00' /root/demo/cp/u-dst.txt
$ cp -u /root/demo/cp/u-src.txt /root/demo/cp/u-dst.txt && cat /root/demo/cp/u-dst.txt
newer
第一步:源 u-src.txt 刚创建(2026-10-15),目标 u-dst.txt 被设为 2025-01-01,源较新,cp -u 执行复制(cat 显示目标内容已变成 newer)。第二步:把目标 mtime 改到 2026-10-15 23:59(比源新),再执行 cp -u 时被跳过,目标内容保持不变。适合配合 -a 做简易增量同步:cp -au 源目录 目标目录。
3.6 -i:覆盖已有文件前交互确认
-i(--interactive)在目标已存在时先询问。用管道模拟键盘输入验证两种回答:
$ printf 'y\n' | cp -i /root/demo/cp/src/file.txt /root/demo/cp/copy.txt
cp: overwrite '/root/demo/cp/copy.txt'?
$ cat /root/demo/cp/copy.txt
line1
line2
回答 y 后目标被覆盖(内容仍是 file.txt 的内容)。回答 n 则跳过且退出码为 0:
$ printf 'new\n' > /root/demo/cp/i-src.txt
$ printf 'n\n' | cp -i /root/demo/cp/i-src.txt /root/demo/cp/copy.txt; echo exit=$?
cp: overwrite '/root/demo/cp/copy.txt'? exit=0
$ cat /root/demo/cp/copy.txt
line1
line2
回答 n 后目标内容保持不变,exit=0——拒绝覆盖不视为错误。交互式复制适合人工清理或手工覆盖场景;脚本里无人值守时用 -n(3.7)或 -f(3.8)明确表达策略。
3.7 -n:不覆盖已有文件
-n(--no-clobber)遇到已存在的目标直接不覆盖,且无任何输出、退出码 0:
$ cp -n /root/demo/cp/i-src.txt /root/demo/cp/copy.txt; echo exit=$?
exit=0
$ cat /root/demo/cp/copy.txt
line1
line2
copy.txt 内容未变(没有被 i-src.txt 的 new 覆盖)。注意 -n 是静默跳过,脚本里如果想区分"复制成功"和"因存在而跳过",靠退出码做不到,需要先自行判断目标是否存在。帮助原文注明 -n 会覆盖之前的 -i 选项,两者不要混用。
3.8 -f:目标打不开时先删除再复制(普通用户场景实测)
-f(--force)帮助原文:"if an existing destination file cannot be opened, remove it and try again"。root 用户几乎总能打开目标文件,差别体现不出来;换成普通用户(实测用 setpriv --reuid=65534 以 nobody 身份执行)就有可见差异。先造一个 nobody 可写目录里的只读文件:
$ mkdir -p /tmp/cp-f && chmod 777 /tmp/cp-f
$ printf 'newcontent\n' > /tmp/cp-f/src.txt
$ printf 'protected\n' > /tmp/cp-f/dst.txt && chmod 444 /tmp/cp-f/dst.txt
$ setpriv --reuid=65534 --regid=65534 --clear-groups cp /tmp/cp-f/src.txt /tmp/cp-f/dst.txt; echo exit=$?
cp: cannot create regular file '/tmp/cp-f/dst.txt': Permission denied
exit=1
dst.txt 属主是 root、权限 444,nobody 打不开它写内容,普通 cp 失败退出码 1。加 -f 后 cp 改用"先删除旧文件再新建"的策略(删除动作只需目录写权限,目录归 nobody 所有):
$ setpriv --reuid=65534 --regid=65534 --clear-groups cp -f /tmp/cp-f/src.txt /tmp/cp-f/dst.txt; echo exit=$?
exit=0
$ cat /tmp/cp-f/dst.txt && ls -l /tmp/cp-f
newcontent
total 8
-rw-r--r-- 1 nobody nogroup 11 Oct 15 06:35 dst.txt
-rw-r--r-- 1 root root 11 Oct 15 06:35 src.txt
复制成功,且注意副作用:新建的 dst.txt 属主变成了 nobody(文件是被新建的,不是原地改写)。实测完清理了临时目录:
$ rm -rf /tmp/cp-f
帮助原文还提醒:-f 与 -n 同时使用时 -f 被忽略。
3.9 -v:显示复制过程
-v(--verbose)逐条打印"复制了什么到哪",格式为 '源' -> '目标':
$ cp -v /root/demo/cp/src/file.txt /root/demo/cp/v.txt
'/root/demo/cp/src/file.txt' -> '/root/demo/cp/v.txt'
$ cp -v -r /root/demo/cp/src /root/demo/cp/vdir
'/root/demo/cp/src' -> '/root/demo/cp/vdir'
'/root/demo/cp/src/sub' -> '/root/demo/cp/vdir/sub'
'/root/demo/cp/src/sub/a.txt' -> '/root/demo/cp/vdir/sub/a.txt'
'/root/demo/cp/src/file.txt' -> '/root/demo/cp/vdir/file.txt'
'/root/demo/cp/src/link.lnk' -> '/root/demo/cp/vdir/link.lnk'
递归复制时逐层列出每个被复制的条目,核查大目录复制是否完整时非常实用。
3.10 -b:覆盖前为已有目标生成备份
-b(--backup 无参形态)在覆盖已有目标前,先把旧目标改名为 原名~ 留底:
$ printf 'b1\n' > /root/demo/cp/b-dst.txt
$ printf 'b2\n' > /root/demo/cp/b-src.txt
$ cp -b /root/demo/cp/b-src.txt /root/demo/cp/b-dst.txt && ls -l /root/demo/cp/b-dst.txt*
-rw-r--r-- 1 root root 3 Oct 15 06:35 /root/demo/cp/b-dst.txt
-rw-r--r-- 1 root root 3 Oct 15 06:35 /root/demo/cp/b-dst.txt~
覆盖后 b-dst.txt 是新内容(3 字节,来自 b-src.txt),旧文件完整保留为 b-dst.txt~。对"允许覆盖但不许丢旧数据"的场景(如更新配置文件)是稳妥组合;多级备份可用长选项 --backup=numbered 生成编号备份。
3.11 -l:硬链复制(同 inode、链接数 2)
-l(--link)不做数据复制,直接为源文件新建一个硬链接。用 ls -i 与 stat 验证 inode 与链接数:
$ cp -l /root/demo/cp/src/file.txt /root/demo/cp/hard.txt; echo exit=$?
exit=0
$ ls -li /root/demo/cp/src/file.txt /root/demo/cp/hard.txt
31616822 -rw-r----- 2 root root 12 Jan 1 2025 /root/demo/cp/hard.txt
31616822 -rw-r----- 2 root root 12 Jan 1 2025 /root/demo/cp/src/file.txt
$ stat -c 'inode=%i links=%h name=%n' /root/demo/cp/src/file.txt /root/demo/cp/hard.txt
inode=31616822 links=2 name=/root/demo/cp/src/file.txt
inode=31616822 links=2 name=/root/demo/cp/hard.txt
两个名字 inode 同为 31616822、链接数同为 2——磁盘上只有一份数据。属性(含 mtime 2025)也天然一致,因为本就是同一个 inode。与 ln 命令语义相同,cp -l 是"本想复制、改为链接"的场景化封装。
3.12 -s:符号链接复制
-s(--symbolic-link)同样不复制数据,改为创建一个指向源的符号链接:
$ cp -s /root/demo/cp/src/file.txt /root/demo/cp/sym.txt; echo exit=$?
exit=0
$ ls -l /root/demo/cp/sym.txt
lrwxrwxrwx 1 root root 26 Oct 15 06:35 /root/demo/cp/sym.txt -> /root/demo/cp/src/file.txt
$ readlink /root/demo/cp/sym.txt
/root/demo/cp/src/file.txt
sym.txt 是软链(大小 26 字节,正好是目标路径串的长度),记录值为绝对路径 /root/demo/cp/src/file.txt。磁盘占用几乎为零,但源文件删除后 sym.txt 即悬空(对比见 3.11 的硬链)。注意 -s 一次只能处理一个源,且目标必须不存在。
3.13 -d:按链接复制链接
-d 等价于 --no-dereference --preserve=links:当源本身是软链时,复制出的是链接而不是它指向的文件:
$ cp -d /root/demo/cp/src/link.lnk /root/demo/cp/d.lnk && ls -l /root/demo/cp/d.lnk
lrwxrwxrwx 1 root root 26 Oct 15 06:35 /root/demo/cp/d.lnk -> /root/demo/cp/src/file.txt
d.lnk 是与源同指向的软链。不加 -d 时(默认行为)软链源会被解引用,复制出来的是指向文件的一份实体副本——需要"连链接一起搬"时用 -d,这正是 -a 包含它的原因。
3.14 -t:显式指定目标目录
-t(--target-directory)把目标目录提到参数前面,方便一次复制多个文件:
$ cp -t /root/demo/cp/src/sub /root/demo/cp/v.txt && ls -l /root/demo/cp/src/sub
total 8
-rw-r--r-- 1 root root 4 Oct 15 06:35 a.txt
-rw-r----- 1 root root 12 Oct 15 06:35 v.txt
v.txt 被复制进 src/sub。cp -t 目录 源1 源2 形态下源文件可以列很多个,脚本里拼接参数列表时比"先列源再列目录"更好组装。
3.15 -T:把目标当普通文件(控制"进目录"行为)
目标已存在且是目录时,cp -r 源 目标 会把源复制为目标的子目录;-T(--no-target-directory)强制把目标当普通条目处理,改为复制源的内容:
$ mkdir -p /root/demo/cp/exists1 /root/demo/cp/exists2
$ cp -r /root/demo/cp/src /root/demo/cp/exists1 && ls /root/demo/cp/exists1
src
$ cp -rT /root/demo/cp/src /root/demo/cp/exists2 && ls /root/demo/cp/exists2
file.txt
link.lnk
sub
同为"目标目录已存在":不带 -T 时 exists1 里多了一层 src(src 被复制进 exists1);带 -T 时 exists2 里直接是 src 的内容(file.txt、sub、link.lnk)。写同步脚本时若想精确控制"复制成什么",-T 能消除这个歧义。
四、实战示例
4.1 保留属性的目录树备份
备份目录树并保持时间戳、权限、链接形态不变,验证备份完整性:
$ cp -a /root/demo/cp/src /root/demo/cp/acopy && ls -lR /root/demo/cp/acopy
/root/demo/cp/acopy:
total 8
-rw-r----- 1 root root 12 Jan 1 2025 file.txt
lrwxrwxrwx 1 root root 26 Oct 15 06:35 link.lnk -> /root/demo/cp/src/file.txt
drwxr-xr-x 2 root root 4096 Oct 15 06:35 sub
/root/demo/cp/acopy/sub:
total 4
-rw-r--r-- 1 root root 4 Oct 15 06:35 a.txt
ls -l 直接可见备份内文件的时间戳与源一致(Jan 1 2025),软链也保留为链接。追加 -u 可实现增量:cp -au 源目录 目标目录。
4.2 多个文件汇总复制到指定目录
把零散文件收进一个目录,-t 让命令在脚本中更好组装:
$ cp -t /root/demo/cp/src/sub /root/demo/cp/v.txt && ls -l /root/demo/cp/src/sub
total 8
-rw-r--r-- 1 root root 4 Oct 15 06:35 a.txt
-rw-r----- 1 root root 12 Oct 15 06:35 v.txt
v.txt 落入目标目录,ls 确认。多文件时写成 cp -t 目标目录 文件1 文件2 即可,不必改动源顺序。
4.3 免拷贝建立文件引用
对大文件建"第二入口"而不占双倍空间,用 -l 硬链复制并验证同 inode:
$ cp -l /root/demo/cp/src/file.txt /root/demo/cp/hard.txt; echo exit=$?
exit=0
$ ls -li /root/demo/cp/src/file.txt /root/demo/cp/hard.txt
31616822 -rw-r----- 2 root root 12 Jan 1 2025 /root/demo/cp/hard.txt
31616822 -rw-r----- 2 root root 12 Jan 1 2025 /root/demo/cp/src/file.txt
inode 相同、链接数为 2,磁盘上只有一份数据。适合日志、数据集这类"多处引用同一份内容"的场景;代价是两个名字永远同步变化,需要独立版本时应改用真复制。
五、注意事项与常见问题
Q1:为什么复制出来的文件修改时间变了?
cp 默认只复制数据与(可能的)权限,不保留时间戳;mtime 是复制发生的时刻。需要保留时加 -p(mode/ownership/timestamps)或 -a(全部属性,见 3.3、3.4 的 stat 对照)。
Q2:复制目录报 omitting directory 怎么办?
cp 对目录默认拒绝处理,加 -r 递归复制(见 3.2 的报错与正确做法)。
Q3:cp -i 回答 n 之后退出码是多少?
0。拒绝覆盖不视为错误,命令静默跳过(见 3.6 的 exit=0 实测)。
Q4:-i、-n、-f 同时写在一条命令里会怎样?
帮助原文明确:-i 覆盖之前的 -n,-n 覆盖之前的 -i,-f 在有 -n 时被忽略。原则是后写的生效,但强烈建议不要混用,语义靠单选项表达(见 2.2 说明)。
Q5:root 用户用 -f 怎么看不出效果?
root 几乎总能直接打开已有目标文件,-f 的"先删再建"路径根本不会触发。本文用普通用户(setpriv 切到 nobody)实测出了差别:普通 cp 报 Permission denied,-f 成功(见 3.8)。
Q6:cp -l 和 ln 建的硬链接是一回事吗?
是。两者都是为同一 inode 增加一个目录项,验证方式相同(同 inode、链接数增加,见 3.11)。cp -l 的定位是"复制动作改为链接",语义等价、写法偏好问题。
Q7:cp -s 和 ln -s 有什么区别?
都是建软链,但 cp -s 目标必须不存在、一次只能一个源、以 SOURCE 的写法记录链接值;ln -s 更灵活(支持 -f 覆盖、多源进目录)。常规建软链用 ln -s,cp -s 见 3.12 实测。
Q8:-r 递归复制时,目录里的软链会被展开成实体文件吗?
实测不会。目录树内遇到的软链按链接本身复制(lrwxrwxrwx、指向不变,见 3.2); -a 在此基础上再保留全部属性(见 3.4)。
六、参考链接
- GNU coreutils 官方手册(cp 章节):https://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html
- 在线文档:https://www.gnu.org/software/coreutils/cp
- 本机帮助:
cp --help、man cp(Ubuntu 22.04 内置) - 关联手册页:
man ln(硬链接/软链接机制)、man symlink(软链解析)