这个命令是干啥的
cp 是 Linux 下最常用的文件拷贝命令,全称 copy。新手一般只会 cp a.txt b.txt,但真到了运维场景,拷个大文件或者批量复制目录的时候,各种坑就来了。
我刚开始干活那会儿,有一次要从测试服务器拷一个 15G 的数据库备份文件到生产服务器。直接用 cp 扔那不管了,结果半小时后一看,终端断了,文件拷了一半。后来才跪着学完 cp 的各种参数。
cp 的核心行为分两种情况:同一分区拷 和 跨分区拷。同一分区下,cp 实际上是在目录项里新增一条记录,然后复制数据块。跨分区就麻烦了,得先读出来再写过去,速度慢很多。
基本用法(3分钟上手)
最简单的用法:
# 复制单个文件
cp source.txt dest.txt
# 复制到目录
cp source.txt /target/dir/
# 复制整个目录(必须加 -r)
cp -r mydir/ /backup/mydir/
# 保留文件属性(时间戳、权限、所有者)
cp -a source.txt dest.txt
-a 这个参数最常用,它等于 -dR --preserve=all,也就是递归复制、保留软链接、保留所有属性。做备份的时候基本必加。
进阶骚操作
1. 只拷贝更新的文件(-u)
-u 参数只在源文件比目标文件新的时候才复制。这玩意在增量备份场景特别好用:
# 只复制更新的文件
cp -u source.txt backup/source.txt
# 配合 -a 做增量目录备份
cp -au sourcedir/ backupdir/
我第一次用 -u 是在一次日增量备份脚本里。每天凌晨跑一次,把当天修改过的配置文件拷到备份目录。之前写了个全量 cp 的脚本,5 分钟跑不完。换成 -u 后,十几秒就完事了。
2. 交互式确认(-i)
# 覆盖前询问
cp -i important.txt /backup/
默认 cp 如果目标文件存在会直接覆盖,不会问你。加了 -i,每次覆盖都会弹确认。我一般不在脚本里用 -i(会卡住),但在手动操作重要文件时必加。
3. 显示拷贝进度
cp 本身没有内置进度条。有两种办法搞:
方法一:用 -v 看文件列表
# 显示每个文件的拷贝情况
cp -av sourcedir/ destdir/
但大文件还是看不到进度百分比。
方法二:用 rsync 替代
# rsync 显示进度
rsync -avh --progress bigfile.tar.gz /backup/
这个是我实际用的最多的。rsync -avh --progress 会显示百分比、速度、剩余时间。拷大文件没有进度条是非常焦虑的体验。
方法三:用 progress 工具
# 单独开一个终端看 cp 的进度
progress -m cp
这是一个叫 progress 的小工具,可以实时显示当前正在运行的 cp、mv、dd 等命令的进度。装一下:apt install progress。
4. 强制覆盖和备份
# 覆盖时不提示(默认就是这行为)
cp -f source.txt dest.txt
# 目标存在时先备份(加后缀)
cp -b source.txt dest.txt # 会生成 dest.txt~
cp --backup=numbered source.txt dest.txt # 生成 dest.txt.~1~
-b 这个参数救过我一次。有一次批量替换配置文件,cp -b 自动给每个原文件生成了备份。后来发现改错了,.bak 直接从备份文件名里找回来。
避坑指南
坑1:目录末尾的斜杠
# 这俩效果不一样!
cp -r mydir/ /backup/ # 复制 mydir 的内容到 /backup/
cp -r mydir /backup/ # 复制 mydir 整个目录到 /backup/
如果目标目录不存在,第二个命令会在 /backup/ 下创建一个 mydir 目录。第一个直接把内容拷进去。我在这上面翻车过好几次,写脚本的时候特别容易搞混。
坑2:跨分区硬链接陷阱
# 报错:Invalid cross-device link
ln source.txt /other_partition/link.txt
# 正确做法:用 cp
cp source.txt /other_partition/link.txt
硬链接不能跨文件系统(分区、挂载点)。我以前不知道这个,写了一个自动化脚本想用硬链接来省空间,结果跑了几天发现全是拷贝,空间直接爆炸。
坑3:大文件终端断开
拷贝大文件(比如超过 10G)的时候,直接用 cp 在前台跑,SSH 一断就没了。建议这样搞:
# 用 nohup 放后台
nohup cp bigfile.tar.gz /backup/ &
# 或者用 screen / tmux
screen -S copyjob
cp bigfile.tar.gz /backup/
# Ctrl+A D 分离,后面再 screen -r 回来
我现在基本养成习惯,拷大文件必开 tmux。你可以用 nohup cp ... &,但不如 tmux 灵活(还能回来看看状态)。
坑4:cp -r 不会复制隐藏文件
# 这不会复制 .hidden 文件
cp -r mydir/ newdir/
# 要这样
cp -r mydir/. newdir/
. 通配符默认不匹配以点开头的文件。如果想把隐藏文件也拷过去,用 cp -r sourcedir/. destdir/ 或者开启 dotglob shell 选项。
实战场景(重点!结合真实运维场景)
场景:服务器日志归档
线上服务器每天产生几百 MB 的日志,需要每天做增量归档:
#!/bin/bash
# 定义源日志目录和目标归档目录
SRC="/var/log/myapp/"
DST="/backup/logs/$(date +%Y%m%d)/"
# 创建今天的归档目录
mkdir -p "$DST"
# 使用 cp -au 做增量复制,只拷当天新增或修改的文件
cp -auv "$SRC" "$DST"
# 记录日志
echo "$(date) 日志归档完成,从 $SRC 到 $DST" >> /var/log/backup.log
这个脚本我跑了两年没出过问题。关键就是用 -u 避免重复拷贝,用 -a 保留文件时间戳方便回溯。
场景:跨服务器大文件传输
从生产服务器拷 50G 的数据库备份到备份服务器:
# 终极方案:rsync + 进度 + 断点续传 + 压缩
rsync -avhP --compress --partial \
/data/mysql_backup.sql.gz \
root@backup-server:/backup/mysql/
# 常用参数说明:
# -a 归档模式
# -v 显示详情
# -h 人类可读大小
# -P 等于 --partial 断点续传 + --progress 进度
# --compress 传输时压缩
如果网络断了,再跑一次同样的命令,--partial 会接着上次的位置继续传,不会从头开始。这个机制比 cp 强太多了。
场景:容器内拷贝文件
# 从容器拷出来
docker cp mycontainer:/app/logs/app.log ./app.log
# 拷进容器
docker cp ./config.yaml mycontainer:/app/config.yaml
Docker 的 cp 语法和 Linux cp 类似,但注意它只能操作单个路径,不支持 -r 时自动递归。
今日作业
你的 web 服务器日志目录 /var/log/nginx/ 里面有 .log 和 .gz 两种文件。写一条命令把今天新增或修改的日志文件(包括隐藏文件)拷贝到 /backup/nginx/ 目录下,保留文件属性和时间戳,并且显示每个文件的拷贝过程。
(提示:用 cp -au 加 -v,注意目录斜杠的位置,想清楚.文件要不要拷贝。)