第 2 章 · 03 增删改:`cp`、`mv`、`rm`


第 2 章 · 03 增删改: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,精选并套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 用 cp 复制文件和目录,理解 -r(递归)、-i(确认)、-f(强制)、-p(保留属性)、-v(显示过程)的取舍。
  2. 用 mv 移动和改名,理解「同文件系统下 mv 是秒级、跨文件系统是复制+删除」的原理。
  3. 解释 mv 的「同名秒替」陷阱——目标已存在时会被无声覆盖。
  4. 用 rm 删除文件和目录,理解 -r、-f、-i 的含义,知道为什么 rm -rf 危险。
  5. 在生产环境建立对破坏性命令的「敬畏」,知道该加什么保护(-i、别名、trash)。

一、设计动机:复制、移动、删除的底层

这三条命令的「表象」是复制、移动、删除,但它们的「底层」差异巨大,理解底层才能避开坑。

1. 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 表态「我知道我在递归复制整个目录树」。

2. 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」会卡半天。

3. rm:删除 = 减硬链接计数

rm 的本质不是「把数据抹掉」,而是「解除文件名与 i 节点的链接」(从目录项里删掉这个文件名)。当一个 i 节点的硬链接数减到 0(没有任何文件名指向它),且没有进程持有它时,内核才回收它的数据块。这就是第 05 节要讲的「硬链接」的核心——rm 只是摘掉一个名字,不是删数据。

rm a.txt # 解除 a.txt 与其 i 节点的链接 rm -r src # 递归删除 src 目录及其所有内容 ​

二、高频组合与实战

1. 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。

2. 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。这个「存在与否决定行为」的二义性,是新人最容易翻车的地方。

3. 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 删了就是真删了。后文「破坏力边界」专门讲怎么防。

三、踩坑与排错

坑 1:mv 的「同名秒替」陷阱

mv new_config.txt current_config.txt # current_config.txt 已存在? 直接覆盖, 无确认! ​

mv 默认会无声覆盖已存在的目标文件。如果你以为它会「保留原文件、只加新的」,那就大错特错——原来的文件瞬间消失,没有任何提示。这就是「秒替」陷阱。

防范:交互式操作养成习惯加 -i;或者先 ls 看目标是否存在;或者干脆先 cp -i 再 rm。生产环境的配置文件改名,宁可慢一点也别丢数据。

坑 2:变量为空 + rm -rf = 灾难

rm -rf /opt/app/$VERSION/ # 如果 $VERSION 为空, 这变成 rm -rf /opt/app/ ! ​

这是最经典的删库事故。变量为空时,路径拼接出问题,rm -rf 删掉的范围远超预期。

防范:几个铁律——

  1. 脚本里关键的 rm -rf 前必须校验变量非空:[ -n "$VERSION" ] || exit 1。
  2. 用 ${var} 包裹变量名,让边界清晰:rm -rf /opt/app/${VERSION}/。
  3. 危险路径加一道保险:rm -rf /opt/app/${VERSION} 不带末尾 /,稍微安全一点(虽然不能根治)。
  4. 终极保险:用 find ... -delete 或 trash 命令代替直接 rm -rf。

坑 3: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 看目标是否存在,再决定怎么复制。

坑 4:通配符 + rm 的意外匹配

rm *.bak # 如果当前目录没有 .bak 文件, 通配符不展开 # 某些 Shell 配置下, *.bak 原样传给 rm, 报错但无害 rm * .bak # 灾难: 多了个空格, * 匹配一切, 先删光当前目录! ​

rm * .bak 这种「多一个空格」的笔误,会先执行 rm *(删光当前目录所有文件),然后才尝试删 .bak。这种错误一旦发生不可逆。

防范:rm 配通配符时,先用 echo * 或 ls * 看通配符会展开成什么,确认无误再换成 rm。关键目录禁用 rm *。

坑 5:跨文件系统 mv 失败时的半成品

mv /tmp/big.bin /mnt/ # 跨文件系统: 复制到一半磁盘满了 # 结果: 源还在, 目标是个半成品 ​

跨文件系统的 mv(本质是复制+删除)如果中途失败(磁盘满、权限错、网络断),可能留下半成品文件在目标位置,源文件还在。这种情况下你需要手动清理半成品,然后重试或改用 cp + 验证 + rm 的两步法。

四、破坏力边界:对 rm -rf 建立敬畏

rm -rf 之所以危险,不只是「删得快」,而是它的几个特性叠加:不问、不停、递归、无回收站。下面是几条「永远不要」的铁律:

# 永远不要敲的命令(列举反面教材,切勿执行) rm -rf / # 删根, 系统瞬间报废 rm -rf $UNDEFINED_VAR/... # 变量为空, 路径错位 rm -rf . # 删光当前目录 rm -rf * # 删光当前目录, 通配符误用风险极高 ​

实用的防护手段:

  1. 交互式加 -i:把 rm 别名为 rm -i,删前必问。生产服务器上这是常见配置。
  2. 用 trash 命令:很多发行版有 trash-cli,trash xxx 把文件送回收站,可恢复。
  3. 关键操作前先 mv 到临时目录:把「要删的」先 mv 到 /tmp/to_delete/,观察一阵再清,给自己后悔机会。
  4. 脚本里 set -u:让 Shell 在变量未定义时报错,拦截 $UNDEFINED 的空值灾难。

⚠️ 注意:rm -rf 删的数据,理论上数据块在被覆盖前还能用专业工具(如 extundelete、TestDisk)恢复一部分,但成功率低、成本高。不要指望「删了还能找回来」,把它当成不可逆操作对待。

本节要点回顾

  1. cp 是真复制(读+写数据),目录必须加 -r;-p 保留属性、-v 显示过程、-i 防覆盖。
  2. mv 同文件系统是「改目录项」秒级完成,不搬数据;跨文件系统退化为「复制+删除」,会慢。
  3. mv 的二义性:mv dir1 dir2,dir2 不存在则改名,存在则移进——务必先确认目标是否存在。
  4. mv 的「同名秒替」陷阱:目标已存在会被无声覆盖,养成加 -i 的习惯。
  5. rm 的本质是「解除文件名与 i 节点的链接」,不是抹数据——这为第 05 节的硬链接埋伏笔。
  6. rm -rf 是高危组合,铁律:校验变量非空、警惕通配符、set -u、关键删改先 mv 到临时区。
  7. Linux 无回收站,rm 即不可逆——建立敬畏,用 trash 或 -i 给自己留后路。

文件层面的增删改讲完了,但目录还有两个专门的工具:mkdir(建)和 rmdir(删空目录)。下一节会讲清 mkdir -p 为什么是脚本里的「幂等妙用」(造目录必加 -p),以及 rmdir 为什么固执地「只删空目录」——这个看似限制的设计,其实是给你的安全垫。


作者与出处
原作者: 灏天文库
来源:jaywcjlove
许可证:MIT
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: 灏天文库 转发
评论区 (0)
U