cp 命令详解与参数实测(Ubuntu 22.04)

GNU cp 参数实测:默认复制、-r 递归、-p/-a 属性保留、-u/-i/-n/-f 覆盖控制、-b 备份、-l 硬链与 -s 软链复制,Ubuntu 22.04 实测。

Linux命令Linux系统UbuntuUbuntu 22.04命令行文件管理2026/10/15

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)。

六、参考链接