Bash cp 命令详解
cp
是 “copy” 的缩写。该命令的功能是复制文件和目录。可以把源文件(SOURCE)复制到目标文件(DEST),或者把多个源文件(SOURCEs)或目录复制到目录(DIRECTORY)中。
cp 命令语法和简单使用
语法:
cp [OPTION]... [-T] SOURCE DEST
cp [OPTION]... SOURCE... DIRECTORY
cp [OPTION]... -t DIRECTORY SOURCE...
-
在第一种格式中,如果提供了两个参数,第一个参数是源文件,第二参数是目标文件(可以不存在),
cp
命令把源文件(SOURCE)复制到目标文件(DEST)中。如果指定了
-T
选项,则第二参数只能是文件,不能是目录。]# cp foo.txt bar.txt ]# ls -l total 8 -rw-r--r--. 1 root root 13 Oct 21 19:39 bar.txt -rw-r--r--. 1 root root 13 Oct 21 19:38 foo.txt
-
在第二种格式中,拷贝多个源文件到一个目录中。这种格式最后的参数必须是一个目录,不然,多个源文件复制到哪里去?
]# ls file1 file2 file3 file4 file5 file6 # 可以使用通配符匹配多个文件 ]# cp file* backup/ ]# ls backup/ file1 file2 file3 file4 file5 file6
-
第三种格式与第二格式相似,只不过通过一个选项
-t, --target-directory
来指定目标目录,并把所有源文件放在后面而已。]# cp -t target/ foo.txt bar.txt ]# ls target/ bar.txt foo.txt
cp 命令的选项说明及示例
该命令的选项比较多,用户如果没有一定的文件系统基础知识,对文件相关的属性不太了解,那么要理解该命令的一些选项就比较难。毕竟我们平常在Windows中复制文件时基本不考虑这些方面。
-
Linux 文件拥有许多属性,例如属主、属组、权限、时间戳、链接,安全上下文等。因此在复制时可以通过选项来控制这些属性。
属性 说明 mode 模式 保留文件的模式位,等价于使用 chmod
进行过设置和任何ACLs
(文件访问列表)。ownership 属主属组 保留文件的属主、属组,等同于使用 chown
进行过设置。但是,保留住这些属性的能力受到与使用chown
相同的限制。timestamps 时间戳 保留文件的最后访问和修改时间(atime和mtime),等同于在使用 touch
时一样,尽可能保留。links 链接 在目标文件中保留与源文件之间的任何链接。使用 -L
或-H
,此选项可能会将符号链接复制为硬链接。context 安全上下文 保留源文件的 SELinux 安全上下文,或者如果使用详细诊断时失败。 xattr 扩展属性 保留源文件的扩展属性,或者在使用详细的诊断时失败。 all 所有 保留以上所有的属性。与单独指定上述所有属性相同,但是在复制 context
或xattr
失败时,将不会输出失败的退出状态。 -
一种特殊的文件——稀疏文件:这种文件可能显示体积很大,但是实际占用磁盘空间很小。其包含许多"空洞",而空洞由一系列零字节组成,不占用任何物理磁盘空间。当读取这种文件时,这些空洞当成零被读取,因此可以节省磁盘空间。
创建稀疏文件有多种方法,例如第一种方法:
$ truncate -s 100G big_file $ ll -hs big_file 0 -rw-rw-r--. 1 jian jian 100G Oct 23 19:10 big_file
ll -hs
命令能够打印出许多信息,其中的选项-s
取得实际占用空间的大小,显示第一字段的值 0,代表文件实际占用磁盘空间为 0,而 100G 表示文件的大小,这是一个逻辑概念,其读自于 inode 区记录的文件元数据中的信息。记住:文件大小和实际占用空间是两个概念。创建稀疏文件第二种方法:使用
dd
命令创建稀疏文件:$ dd if=/dev/zero of=fs.img bs=1G seek=1024 count=0 $ ll -sh fs.img 0 -rw-rw-r--. 1 jian jian 1.0T Oct 23 19:33 fs.img
稀疏文件采用的是预分配机制,即当您创建一个 1T 大小的文件时,只在 inode 区记录好文件的元数据,如果该文件没有内容,这个时候其实没有实际分配空间,后面根据需要逐步分配空间。这样做的好处有:
- 预分配,就是把分配空间的工作放在了初始化时进行,避免了在需要时现场进行分配的开销。
- 如果不提前占坑,很有可能等到需要时已经没有空间够用,因此把物理空间先占好,就可以心安使用了。
-
写时复制(CoW, Copy on Write):这需要目标文件系统支持才行。执行复制的一瞬间只复制元数据(inode),所以拷贝速度极快,目标文件的内容与源文件共享,只在源文件进行写操作时才进行真正的复制。这与虚拟机的快照原理一样,这对于持续进行更新的数据进行备份很重要。
-
软连接文件如何处理和目标文件已经存在时如何操作都有许多选项。
按照是否复制文件的内容进行分类
按照本人的理解,该命令主要涉及到两个对象:即文件和目录。目录相对简单,而文件则比较复杂。
按照执行复制时是否默认复制源文件的内容进行分类:
-
可直接复制文件内容的文件:大部分普通文件、可执行文件、硬链接文件都是这种类型。
-
软连接文件:默认是复制链接文件所指向的文件。可以通过选项来控制是否要拷贝软连接文件本身。细节比较多,请看示例:
这是当前文件夹现状:
$ ll total 4 -rw-rw-r--. 1 jian jian 13 Oct 23 11:53 file lrwxrwxrwx. 1 jian jian 4 Oct 23 11:53 file.ln -> file
-
--preserve=links
:保留链接如果没有使用
--no-dereferenc
,就算使用了--preserve=links
,也不会复制链接本身。$ cp --preserve=links file.ln file.ln.bak $ ll total 8 -rw-rw-r--. 1 jian jian 13 Oct 23 11:53 file lrwxrwxrwx. 1 jian jian 4 Oct 23 11:53 file.ln -> file -rw-rw-r--. 1 jian jian 13 Oct 23 11:54 file.ln.bak
-
-d
:不要拷贝指向所引用的文件,保留源文件链接本身只有使用
-d
=--no-dereference --preserve=links
,才能真正复制链接文件本身。$ cp --no-dereference --preserve=links file.ln file.ln.bak2 ]$ ll total 8 -rw-rw-r--. 1 jian jian 13 Oct 23 11:53 file lrwxrwxrwx. 1 jian jian 4 Oct 23 11:53 file.ln -> file -rw-rw-r--. 1 jian jian 13 Oct 23 11:54 file.ln.bak lrwxrwxrwx. 1 jian jian 4 Oct 23 11:54 file.ln.bak2 -> file
如果在复制时没有以绝对路径指定元软链接文件,并把该文件本身复制到其它目录中,该备份文件不能正常使用,
$ cp -d file.ln subdir/file.ln.bak3 $ ll subdir/file.ln.bak3 total 0 lrwxrwxrwx. 1 jian jian 4 Oct 23 16:01 file.ln.bak3 -> file # 此时看到软连接存在,但是显示找不到源文件 $ ln -s ../file file.ln # 采用不是复制方式 $ ll total 0 lrwxrwxrwx. 1 jian jian 7 Oct 23 16:14 file.ln -> ../file lrwxrwxrwx. 1 jian jian 4 Oct 23 16:01 file.ln.bak3 -> file
从上面可以看到,两个链接文件的大小不同,里面保存的路径不同。因此,这种情况下,要么在当前目录创建,或者源文件使用绝对路径,就不会出现问题。
-
-H
如果命令行上的一个参数是符号链接文件,则拷贝所引用的文件,而不是链接文件本身。但是如果在递归过程中发现符号链接,则复制链接文件本身,而不是所引用的文件。
$ tree . ├── file ├── file.ln -> file ├── subdir1 │ ├── foo │ └── foo.ln -> foo └── subdir2
这时复制中有两个源,一个是链接文件(出现在命令行上),另一个是包含链接文件的文件夹,
-R
表示递归复制。$ cp -HR file.ln subdir1 subdir2 $ tree . ├── file ├── file.ln -> file ├── subdir1 │ ├── foo │ └── foo.ln -> foo └── subdir2 ├── file.ln └── subdir1 ├── foo └── foo.ln -> foo
验证结果:
$ ll subdir2/ total 8 -rw-rw-r--. 1 jian jian 13 Oct 23 14:59 file.ln drwxrwxr-x. 2 jian jian 4096 Oct 23 14:59 subdir1 # 可以看到 "file.ln" 已经不是链接文件本身,而是所引用的文件的备份了。 $ ll subdir2/subdir1/ total 4 -rw-rw-r--. 1 jian jian 5 Oct 23 14:59 foo lrwxrwxrwx. 1 jian jian 3 Oct 23 14:59 foo.ln -> foo # 这里的 "foo.ln" 仍然是符号链接文件本身。
-
-L
始终复制符号链接所指向的文件,而不是链接文件本身。指定了该选项,
cp
命令不会在目标中创建符号链接。$ tree . ├── file ├── file.ln -> file ├── subdir1 │ ├── foo │ └── foo.ln -> foo └── subdir3 $ cp -LR file.ln subdir1 subdir3/ $ tree . ├── file ├── file.ln -> file ├── subdir1 │ ├── foo │ └── foo.ln -> foo └── subdir3 ├── file.ln └── subdir1 ├── foo └── foo.ln
subdir3/subdir1/foo.ln
已经不是符号链接文件。 -
-P, --no-dereferenc
如果源中遇到符号链接文件,则永远只复制符号链接本身。在其他位置已经有了示例。此处省略。
-
-
特殊文件:有一些文件比较特殊,一般不能直接复制,否则其结果与我们想要的相差太远。例如 /dev/zero,如果直接复制,就会产生一个大文件了。
$ cp /dev/zero file.iso ^C # 过了很短时间通过 `ctrl+c` 予以取消 $ ll file.iso -rw-rw-r--. 1 jian jian 103452672 Oct 23 18:26 file.iso
可以看到复制品并不是原设备文件的备份,而是一个比较大的文件。如果不及时取消操作,复制出来文件可能会很大。要想真正复制该设备文件,需要使用
-a
选项。 -
--copy-contents
当递归操作时,拷贝特殊文件的内容,例如,/dev 下的FIFOs和设备。您通常不想使用该选项,因为它可能会导致不希望看到的结果,例如,永远挂起或者填满整个硬盘。但是,此选项可用于特殊的专家用例。
-
-a, --archive
用于归档该选项等同于
-dR --preserve=all
,其作用是递归、复制链接文件本身及尽可能保留文件的属性。在执行拷贝时,试图尽可能保留原始文件结构、属性和相关元数据。如果正在运行"SELinux",该元数据包含安全上下文。使用
-a
选项可以复制该设备文件本身。通过测试,在CentOS 8 中如果以普通账号登录,不能执行以下命令,即使使用 "sudo " 也不行:# cp -a /dev/zero zero.bak # ll /dev/zero zero.bak crw-rw-rw-. 1 root root 1, 5 Oct 20 09:59 /dev/zero crw-rw-rw-. 1 root root 1, 5 Oct 20 09:59 zero.bak
对于
-a
选项也不能完全保证拷贝过来的文件就与源文件一样。
例如普通用户拷贝属主属组都是 root 的文件,拷贝过来后,这两个属性就变成了当前用户的。如果想要保留指定的所有文件属性不变,那么以 root 身份执行可以的,其它用户同样的操作,实际的文件属性会有变化。
$ cp -a /etc/issue issue $ ll /etc/issue issue -rw-r--r--. 1 root root 23 Nov 10 2021 /etc/issue -rw-r--r--. 1 jian jian 23 Nov 10 2021 issue
-
--sparse=WHEN
: 稀疏文件的操作该选项控制复制时稀疏文件的创建。当源文件被诊断为稀疏文件时,
WHEN
定义了复制时应该如何操作。其取值为:WHEN 说明 auto 自动 如果源文件是稀疏文件,那么试图把目标文件创建为稀疏文件。如果目标文件存在,且不是普通文件,则不让其成为稀疏文件。这是默认值。 always 总是 对于源文件中每个足够长的零字节序列,都尝试在目标文件中创建稀疏的空洞。如果源文件所在的文件系统不支持稀疏文件,而目标文件所在的文件系统支持,这将非常有用,这时可以根据实际情况可以创建稀疏文件。 never 永不 从不创建输出的稀疏文件。一些特殊文件,例如,交换分区文件(swap)。在这种情况下,应当选择使用该值。 $ cp --sparse=never big_file big_file.bak ^C # 已经花费了很长时间,予以取消 $ ll -hs big* 0 -rw-rw-r--. 1 jian jian 100G Oct 23 19:10 big_file 3.4G -rw-rw-r--. 1 jian jian 3.4G Oct 23 20:06 big_file.bak
使用
never
进行复制会实际占用硬盘空间的。有关稀疏文件的更详细介绍,可以参考这个博文:
-
--REFLINK=[whEN]
:引用链接引用链接就是实现这种 “Copy on Write (CoW 写时复制)”,其选项中的
when
参数可以取值always
和auto
,默认是always
。如果目标文件系统不支持,且取值是always
,则复制失败;如果取值是auto
,则会使用标准复制行为。以下可以参考有关引用链接的知识讲解:
软链接、硬链接和引用链接,以及如何实现文件快速拷贝(cp --reflink)
文件属性的控制
Linux 文件的这些属性在复制时可以通过选项来加以控制。
-
--preserve=[attr_list]
保留指定的以逗号分隔属性,默认:mode, ownership, timestamps。可以取值:mode, ownership, timestamps, links, context, xattr, all。
-
--attributes-only
不拷贝文件的数据,只创建一个具有相同属性的文件。如果目标文件已经存在,不会改变该文件的内容。通过
--preserve
选项您可以精确地控制想要拷贝那个属性。示例:使用该选项只拷贝属性
$ cp --attributes-only /etc/issue . $ ll /etc/issue issue -rw-r--r--. 1 root root 23 Nov 10 2021 /etc/issue -rw-r--r--. 1 jian jian 0 Oct 23 09:33 issue
从上可见,只创建了空文件。文件的状态信息查看如下:
$ stat /etc/issue issue File: /etc/issue Size: 23 Blocks: 8 IO Block: 4096 regular file Device: 802h/2050d Inode: 2097192 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Context: system_u:object_r:etc_t:s0 Access: 2022-10-20 09:59:42.585181783 +0800 Modify: 2021-11-10 08:17:30.000000000 +0800 Change: 2022-10-12 09:44:35.455211960 +0800 Birth: 2022-10-12 09:44:35.455211960 +0800 File: issue Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 802h/2050d Inode: 262151 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 1000/ jian) Gid: ( 1000/ jian) Context: unconfined_u:object_r:user_home_t:s0 Access: 2022-10-23 09:33:20.659995207 +0800 Modify: 2022-10-23 09:33:20.659995207 +0800 Change: 2022-10-23 09:33:20.659995207 +0800 Birth: 2022-10-23 09:33:20.659995207 +0800
如果没有指定
--preserve
具体值,拷贝的属性变化较大。下面指定时间戳:$ cp --attributes-only --preserve=timestamps /etc/issue . ]$ ll total 0 -rw-r--r--. 1 jian jian 0 Nov 10 2021 issue ]$ stat issue File: issue Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 802h/2050d Inode: 262151 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 1000/ jian) Gid: ( 1000/ jian) Context: unconfined_u:object_r:user_home_t:s0 Access: 2022-10-20 09:59:42.585181783 +0800 Modify: 2021-11-10 08:17:30.000000000 +0800 Change: 2022-10-23 09:43:49.264987666 +0800 Birth: 2022-10-23 09:41:51.348989081 +0800
看来保留了文件的访问时间和修改时间。有些属性无法拷贝过来。
]$ cp --attributes-only --preserve=owner /etc/issue . ]$ ll total 0 -rw-r--r--. 1 jian jian 0 Nov 10 2021 issue
文件的属主属组无法保留。
-p
等同于
--preserve=mode,ownership,timestamps
。
创建文件的软硬链接
创建对源文件的软硬链接何尝不是一种“复制”。
-
-s, --symbolic-link
复制时创建符号链接而不是拷贝文件本身。如果目标文件不在当前目录,那么,所有的源文件必须以斜杆开头的绝对路径。
$ cp -s /etc/os-release subdir/ $ ll /etc/os-release os-release lrwxrwxrwx. 1 root root 21 Nov 10 2021 /etc/os-release -> ../usr/lib/os-release lrwxrwxrwx. 1 jian jian 15 Oct 23 20:32 os-release -> /etc/os-release
如果在
~/myfiles/
下有许多文件,想在~/myfiles2/
创建这些文件的软连接可以使用以下两种方法之一:$ cp -s ~/myfiles/* ~/myfiles2 $ cp -Rs ~/myfiles ~/myfiles2
-
-l, --link
创建硬链接而不是拷贝源文件内容。
$ cp -l file file.hd $ ll file file.hd -rw-rw-r--. 2 jian jian 13 Oct 23 11:53 file -rw-rw-r--. 2 jian jian 13 Oct 23 11:53 file.hd
目标文件或文件夹存在
如果目标文件已经存在,则要考虑覆盖时是否提示警告;是否要对目标文件进行备份,以及如果目标文件由于权限不够不能打开时,是否强制覆盖;或者不覆盖等。
-
-i, --interactive
默认情况下,复制命令会默默地覆盖现存文件,因此可能会丢失数据。如果您想在覆盖前予以提示确认,请使用该选项(超越前面的
-n
选项)。在 CentOS 8 中以 root 登录后,
cp
默认已经被设置成别名,会予以提示:]# alias cp alias cp='cp -i'
而普通用户默认没有这个设置,建议自己在
.bashrc
加入alias cp='cp -i'
。 -
强制覆盖,有两种情况:
-
第一种是
-f, --force
,表示如果目标文件存在但是不能打开(原因可能是权限不够),则删除它,再执行一次复制(如果-n
选项也在使用,则该选项忽略)。 -
第二种是
--remove-destination
,表示在试图打开每个现存目标文件之前,删除这些目标文件。
-
-
不覆盖。
-n, --no-clobber
,表示不覆盖现存文件(超越前面的-i
选项)。 -
由于目标文件存在,如果要对目标文件进行备份,有两种格式:
-
第一种是
--backup[=CONTROL]
,为每个现存的目标文件进行备份。通过--backup
选项或着通过VERSION_CONTROL
环境变量可以选择版本控制方法。"CONTROL"的取值有:CONTROL 说明 none, off 不做备份(即使提供了 --backup
)numbered, t 进行数字后缀备份,例如"1, 2, …" existing, nil 如果数字备份存在,则继续数字后缀格式,否则,取值 simple
simple, never 只做简单备份 -
第二种是
-b
等价于不带参数的--backup
,即不论复制几次,目标文件只有一个备份。$ ll file.txt -rw-rw-r-- 1 sam sam 6 Oct 24 11:41 file.txt $ cp -b newdir/file.txt file.txt $ ll file.txt* -rw-rw-r-- 1 sam sam 13 Oct 24 11:42 file.txt -rw-rw-r-- 1 sam sam 6 Oct 24 11:41 file.txt~
-
设置备份文件的后缀
-S, --suffix=SUFFIX
。如果设置该选项,则会覆盖常规备份默认后缀(默认是~
)。$ cp -S .bak newdir/file.txt . $ ll file.txt* -rw-rw-r-- 1 sam sam 13 Oct 24 11:51 file.txt -rw-rw-r-- 1 sam sam 6 Oct 24 11:41 file.txt~ -rw-rw-r-- 1 sam sam 13 Oct 24 11:50 file.txt.bak
使用数字方式,可以保留多个备份
$ cp --backup=t newdir/file.txt . $ ll file.txt* -rw-rw-r-- 1 sam sam 13 Oct 24 11:54 file.txt -rw-rw-r-- 1 sam sam 6 Oct 24 11:41 file.txt~ -rw-rw-r-- 1 sam sam 13 Oct 24 11:51 file.txt.~1~ -rw-rw-r-- 1 sam sam 13 Oct 24 11:50 file.txt.bak $ cp --backup=t newdir/file.txt . $ ll file.txt* -rw-rw-r-- 1 sam sam 13 Oct 24 11:55 file.txt -rw-rw-r-- 1 sam sam 6 Oct 24 11:41 file.txt~ -rw-rw-r-- 1 sam sam 13 Oct 24 11:51 file.txt.~1~ -rw-rw-r-- 1 sam sam 13 Oct 24 11:54 file.txt.~2~ -rw-rw-r-- 1 sam sam 13 Oct 24 11:50 file.txt.bak
作为一种特殊情况,如果提供了
--force --backup
选项,并且源文件和目标文件是同名的现存常规文件时,cp
命令会对源文件进行备份。 -
-
-u, --update
只有当源文件比目标文件更新时,或者目标文件不存在时才拷贝。
$ ls -l --time-style=+"%F %T" . newdir/ .: total 4 -rw-rw-r-- 1 jian jian 0 2022-10-23 12:40:52 file1 -rw-rw-r-- 1 jian jian 0 2022-10-23 12:40:52 file2 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:40:40 file4 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:40:40 file5 drwxrwxr-x 2 jian jian 4096 2022-10-24 06:39:33 newdir newdir/: total 0 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:39:33 file1 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:39:33 file2 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:39:33 file3 $ cp -u newdir/* . $ ls -l --time-style=+"%F %T" . total 4 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:43:09 file1 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:43:09 file2 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:43:09 file3 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:40:40 file4 -rw-rw-r-- 1 jian jian 0 2022-10-24 06:40:40 file5 drwxrwxr-x 2 jian jian 4096 2022-10-24 06:39:33 newdir
file3 文件原来没有,file1 和 file2 被更新。
创建父目录
如果复制时源是多级目录,而目标中没有相应的多级目录,这时如果使用 --parents
选项,则在目标目录下创建所有需要的目录。相当于 mkdir -p
创建目录时,如果没有父目录,则予以创建。
示例:
# 当前目录树情况
$ tree
.
├── dir1
└── dir2
└── dir3
└── file
如果不使用 --parents,这是把目录下的文件复制到对应目录下。
$ cp dir2/dir3/file dir1
$ tree
.
├── dir1
│ └── file
└── dir2
└── dir3
└── file
如果使用 --parents,则 dir1 下面创建必要的目录
$ cp --parents dir2/dir3/file dir1
$ tree
.
├── dir1
│ ├── dir2
│ │ └── dir3
│ │ └── file
│ └── file
└── dir2
└── dir3
└── file
其它几个选项比较简单
-
T, --no-target-directory
就是把目标当成一个常规文件。就是第一种格式中的一个选项,其确保最后参数不能是目标。
-
--strip-trailing-slashes
对每个源参数都删除尾随的斜杆。
-
-x, --one-file-system
只对执行命令所在的文件系统进行操作。如果
cp
命令试图跨越边界到另一个文件系统,则跳过这些文件。这包括网络驱动器、另一个分区——任何驻留在具有不同挂载点的文件系统上的文件。表示挂载点本身的目录将被复制,但是不会进行遍历操作。
如果指定了
-v
选项,则可以准确地看到跳过了那些文件。 -
--help
-
--version
最后,提供一个有意思的主意事项:
对于复制文件夹到目标文件夹,如果没有该目标目录,则创建该目录,并把文件都拷贝过去,但是同样的操作如果再次执行,这时已经有了目标目录,就会把源文件拷贝到目标文件夹下,当然,第三次拷贝会提示是否覆盖。