cp、mv、rm本节摘要:增、删、改——这三个动词概括了你对文件做的绝大部分操作。
cp(复制)、mv(移动/改名)、rm(删除)是日常最高频的三条命令,也是最容易闯祸的三条。本节不只教你cp -r src dst,而是讲透几个关键设计:为什么cp复制目录必须加-r(递归)、为什么mv改名是「秒级」的(它只是改目录项,不搬数据)、为什么mv跨文件系统时却退化成「复制+删除」、rm -rf的破坏力边界到底在哪。把这几个「为什么」想清楚,你才能安全地使用这三条命令,避开删库到跑路的经典陷阱。
内容来源:原项目「Linux 命令大全」
command/cp.md、command/mv.md、command/rm.md,精选并套用体系化模板。
阅读完本节,你应当能够:
cp 复制文件和目录,理解 -r(递归)、-i(确认)、-f(强制)、-p(保留属性)、-v(显示过程)的取舍。mv 移动和改名,理解「同文件系统下 mv 是秒级、跨文件系统是复制+删除」的原理。mv 的「同名秒替」陷阱——目标已存在时会被无声覆盖。rm 删除文件和目录,理解 -r、-f、-i 的含义,知道为什么 rm -rf 危险。-i、别名、trash)。这三条命令的「表象」是复制、移动、删除,但它们的「底层」差异巨大,理解底层才能避开坑。
cp:复制 = 读 + 写新数据cp 是最老实的命令——它真的把源文件的数据块读出来,写到目标位置的新文件里。所以复制一个大文件,要花和「读一遍+写一遍」相当的时间和 IO。复制目录时,它需要递归地复制里面的所有文件,这就是 -r 的由来。
cp a.txt b.txt # 把 a.txt 复制成 b.txt cp -r src dst # 把 src 目录(含内容)递归复制成 dst
关键概念:
cp不加-r复制目录会直接报错(cp: omitting directory 'src')。这不是cp笨,而是「复制目录」是个有破坏可能的操作(目标已存在时怎么合并?里面的符号链接怎么办?),需要你显式用-r表态「我知道我在递归复制整个目录树」。
mv:同文件系统下是「改名」,不搬数据mv 是被严重误解的命令。很多人以为「移动就是把数据从一个地方搬到另一个地方」,但在同一个文件系统内,mv 根本不搬数据——它只是改目录项(把文件名从一个目录项挪到另一个目录项),数据块原封不动。所以同文件系统内 mv 一个 10GB 的文件是秒级的。
mv a.txt b.txt # 改名: 同目录下, 秒级(只改目录项) mv /tmp/big.bin /var/ # 同文件系统内移动: 秒级 mv /tmp/big.bin /mnt/ # /mnt 是另一个文件系统(挂载的盘): 慢!复制+删除
为什么跨文件系统就慢了? 因为不同文件系统有不同的 i 节点表、不同的数据块管理方式,i 节点号没法跨系统迁移。这时 mv 退化成「复制一份到目标,然后删除源」——数据真的要读一遍写一遍。理解这点,你就知道为什么「把日志从 /var 移到挂载的数据盘 /data」会卡半天。
rm:删除 = 减硬链接计数rm 的本质不是「把数据抹掉」,而是「解除文件名与 i 节点的链接」(从目录项里删掉这个文件名)。当一个 i 节点的硬链接数减到 0(没有任何文件名指向它),且没有进程持有它时,内核才回收它的数据块。这就是第 05 节要讲的「硬链接」的核心——rm 只是摘掉一个名字,不是删数据。
rm a.txt # 解除 a.txt 与其 i 节点的链接 rm -r src # 递归删除 src 目录及其所有内容
cp 常用组合cp a.txt b.txt # 复制文件 cp -i a.txt b.txt # 目标存在时确认(interactive), 防覆盖 cp -f a.txt b.txt # 强制覆盖(默认行为, 显式写明意图) cp -r src dst # 递归复制目录 cp -p a.txt b.txt # 保留权限/属主/时间戳(preserve) cp -v a.txt b.txt # 显示复制过程(verbose) cp -rp src dst # 递归 + 保留属性(备份目录常用) cp a.txt b.txt /tmp/ # 多源复制: 把多个文件复制到目标目录
💡 技巧:
cp -p在备份配置文件时极有用——不加-p复制出来的文件,属主可能变成你自己,时间戳变成现在;加了-p,权限、属主、修改时间都保留。备份/etc下的配置时,几乎一定要加-p。
mv 常用组合mv a.txt b.txt # 改名(同目录) mv a.txt /tmp/ # 移动到 /tmp(文件名不变) mv a.txt /tmp/b.txt # 移动并改名 mv -i a.txt b.txt # 目标存在时确认(防覆盖!) mv -v a.txt /tmp/ # 显示移动过程 mv dir1 dir2 # dir2 不存在 = 改名; dir2 存在 = 把 dir1 移进 dir2
⚠️ 注意:
mv的最后一条规则很容易踩坑:mv dir1 dir2,如果dir2不存在,是把dir1改名为dir2;如果dir2已存在(是个目录),是把dir1移进dir2里,变成dir2/dir1。这个「存在与否决定行为」的二义性,是新人最容易翻车的地方。
rm 常用组合rm a.txt # 删除文件 rm -i a.txt # 删前确认(推荐交互时用) rm -f a.txt # 强制删(即使不存在也不报错) rm -r src # 递归删目录(目录里所有内容) rm -rf src # 递归 + 强制, 永不确认 rm *.log # 删所有 .log 文件
⚠️ 注意:
rm -rf是 Linux 里最危险的组合之一。-r让它递归钻进所有子目录,-f让它删前不问、出错也不停。一旦路径给错(比如多了一个空格、变量为空),后果不可挽回——Linux 没有「回收站」,rm删了就是真删了。后文「破坏力边界」专门讲怎么防。
mv 的「同名秒替」陷阱mv new_config.txt current_config.txt # current_config.txt 已存在? 直接覆盖, 无确认!
mv 默认会无声覆盖已存在的目标文件。如果你以为它会「保留原文件、只加新的」,那就大错特错——原来的文件瞬间消失,没有任何提示。这就是「秒替」陷阱。
防范:交互式操作养成习惯加 -i;或者先 ls 看目标是否存在;或者干脆先 cp -i 再 rm。生产环境的配置文件改名,宁可慢一点也别丢数据。
rm -rf = 灾难rm -rf /opt/app/$VERSION/ # 如果 $VERSION 为空, 这变成 rm -rf /opt/app/ !
这是最经典的删库事故。变量为空时,路径拼接出问题,rm -rf 删掉的范围远超预期。
防范:几个铁律——
rm -rf 前必须校验变量非空:[ -n "$VERSION" ] || exit 1。${var} 包裹变量名,让边界清晰:rm -rf /opt/app/${VERSION}/。rm -rf /opt/app/${VERSION} 不带末尾 /,稍微安全一点(虽然不能根治)。find ... -delete 或 trash 命令代替直接 rm -rf。cp -r src/ dst 的尾斜杠差异cp -r src dst # dst 不存在: 把 src 复制成 dst; dst 存在: 把 src 复制进 dst/src cp -r src/ dst # 加尾斜杠: 复制 src 的"内容"进 dst(行为略有差异)
源路径尾斜杠的微妙差异:cp -r src dst 复制「src 目录本身」,cp -r src/ dst 复制「src 里的内容」。在目标已存在时,行为差别更大。不确定时,先 ls dst 看目标是否存在,再决定怎么复制。
rm 的意外匹配rm *.bak # 如果当前目录没有 .bak 文件, 通配符不展开 # 某些 Shell 配置下, *.bak 原样传给 rm, 报错但无害 rm * .bak # 灾难: 多了个空格, * 匹配一切, 先删光当前目录!
rm * .bak 这种「多一个空格」的笔误,会先执行 rm *(删光当前目录所有文件),然后才尝试删 .bak。这种错误一旦发生不可逆。
防范:rm 配通配符时,先用 echo * 或 ls * 看通配符会展开成什么,确认无误再换成 rm。关键目录禁用 rm *。
mv 失败时的半成品mv /tmp/big.bin /mnt/ # 跨文件系统: 复制到一半磁盘满了 # 结果: 源还在, 目标是个半成品
跨文件系统的 mv(本质是复制+删除)如果中途失败(磁盘满、权限错、网络断),可能留下半成品文件在目标位置,源文件还在。这种情况下你需要手动清理半成品,然后重试或改用 cp + 验证 + rm 的两步法。
rm -rf 建立敬畏rm -rf 之所以危险,不只是「删得快」,而是它的几个特性叠加:不问、不停、递归、无回收站。下面是几条「永远不要」的铁律:
# 永远不要敲的命令(列举反面教材,切勿执行) rm -rf / # 删根, 系统瞬间报废 rm -rf $UNDEFINED_VAR/... # 变量为空, 路径错位 rm -rf . # 删光当前目录 rm -rf * # 删光当前目录, 通配符误用风险极高
实用的防护手段:
-i:把 rm 别名为 rm -i,删前必问。生产服务器上这是常见配置。trash 命令:很多发行版有 trash-cli,trash xxx 把文件送回收站,可恢复。mv 到临时目录:把「要删的」先 mv 到 /tmp/to_delete/,观察一阵再清,给自己后悔机会。set -u:让 Shell 在变量未定义时报错,拦截 $UNDEFINED 的空值灾难。⚠️ 注意:
rm -rf删的数据,理论上数据块在被覆盖前还能用专业工具(如extundelete、TestDisk)恢复一部分,但成功率低、成本高。不要指望「删了还能找回来」,把它当成不可逆操作对待。
cp 是真复制(读+写数据),目录必须加 -r;-p 保留属性、-v 显示过程、-i 防覆盖。mv 同文件系统是「改目录项」秒级完成,不搬数据;跨文件系统退化为「复制+删除」,会慢。mv 的二义性:mv dir1 dir2,dir2 不存在则改名,存在则移进——务必先确认目标是否存在。mv 的「同名秒替」陷阱:目标已存在会被无声覆盖,养成加 -i 的习惯。rm 的本质是「解除文件名与 i 节点的链接」,不是抹数据——这为第 05 节的硬链接埋伏笔。rm -rf 是高危组合,铁律:校验变量非空、警惕通配符、set -u、关键删改先 mv 到临时区。rm 即不可逆——建立敬畏,用 trash 或 -i 给自己留后路。文件层面的增删改讲完了,但目录还有两个专门的工具:mkdir(建)和 rmdir(删空目录)。下一节会讲清 mkdir -p 为什么是脚本里的「幂等妙用」(造目录必加 -p),以及 rmdir 为什么固执地「只删空目录」——这个看似限制的设计,其实是给你的安全垫。